По умолчанию psql работает в режиме автофиксации:
=> \echo :AUTOCOMMIT
on
Это приводит к тому, что любая команда, выданная без явного указания начала транзакции, сразу же фиксируется. Изменим строку и проверим результат в другом сеансе:
=> UPDATE authors SET first_name = 'Алексей' WHERE last_name = 'Толстой';
UPDATE 1
=> SELECT * FROM authors WHERE last_name = 'Толстой';
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- 5 | Толстой | Алексей | Николаевич (1 row)
Сравните:
=> BEGIN;
BEGIN
=> UPDATE authors SET first_name = 'Лев' WHERE last_name = 'Толстой';
UPDATE 1
=> SELECT * FROM authors WHERE last_name = 'Толстой';
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- 5 | Толстой | Алексей | Николаевич (1 row)
=> COMMIT;
COMMIT
=> SELECT * FROM authors WHERE last_name = 'Толстой';
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- 5 | Толстой | Лев | Николаевич (1 row)
Режим без автофиксации неявно начинает транзакцию при первой выданной команде; изменения надо фиксировать самостоятельно.
=> \set AUTOCOMMIT off
=> UPDATE authors SET first_name = 'Алексей' WHERE last_name = 'Толстой';
UPDATE 1
=> SELECT * FROM authors WHERE last_name = 'Толстой';
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- 5 | Толстой | Лев | Николаевич (1 row)
=> COMMIT;
COMMIT
=> SELECT * FROM authors WHERE last_name = 'Толстой';
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- 5 | Толстой | Алексей | Николаевич (1 row)
Изменения можно откатывать, не прерывая транзакцию (хотя необходимость в этом возникает нечасто).
=> \set AUTOCOMMIT on
=> BEGIN;
BEGIN
=> SAVEPOINT sp;
SAVEPOINT
=> UPDATE authors SET first_name = 'Александр' WHERE last_name = 'Толстой';
UPDATE 1
=> SELECT * FROM authors WHERE last_name = 'Толстой';
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- 5 | Толстой | Александр | Николаевич (1 row)
=> ROLLBACK TO sp;
ROLLBACK
Сейчас изменения откачены, но транзакция продолжается:
=> SELECT * FROM authors WHERE last_name = 'Толстой';
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- 5 | Толстой | Алексей | Николаевич (1 row)
=> UPDATE authors SET first_name = 'Лев' WHERE last_name = 'Толстой';
UPDATE 1
=> COMMIT;
COMMIT
=> SELECT * FROM authors WHERE last_name = 'Толстой';
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- 5 | Толстой | Лев | Николаевич (1 row)
Справедливость восторжествовала.
Оператор подготавливается командой PREPARE:
=> PREPARE s(text) AS SELECT * FROM authors WHERE last_name = $1;
PREPARE
После этого оператор можно вызывать по имени, передавая фактические параметры:
=> EXECUTE s('Толстой');
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- 5 | Толстой | Лев | Николаевич (1 row)
Все подготовленные операторы можно увидеть в представлении:
=> \x
Expanded display is on.
=> SELECT * FROM pg_prepared_statements;
-[ RECORD 1 ]---+-------------------------------------------- name | s statement | PREPARE s(text) AS + | SELECT * FROM authors WHERE last_name = $1; prepare_time | 2017-09-01 15:51:16.099109+03 parameter_types | {text} from_sql | t
=> \x
Expanded display is off.
Помимо сокращения накладных расходов на повторный разбор запроса, подготовленные операторы гарантируют невозможность внедрения SQL-кода. Представим себе, что в приложении есть следующий код:
$query = "SELECT first_name FROM authors WHERE last_name = '$name'";
Если значение $name получено из ненадежного источника (например, введено пользователем на веб-форме), злоумышленник может получить несанкционированный доступ к другим данным.
К чему приведет такое значение?
X' UNION ALL SELECT usename FROM pg_user WHERE '' = '
=> SELECT first_name FROM authors WHERE last_name = 'X' UNION ALL SELECT usename FROM pg_user WHERE '' = '';
first_name ------------ student postgres employee (3 rows)
Мы получили доступ к данным другой таблицы, изменив структуру запроса.
При использовании подготовленных операторов это невозможно, так как структура SQL-запроса фиксируется при синтаксическом разборе. Выражение всегда останется выражением и не сможет превратиться, скажем, в имя таблицы.
=> EXECUTE s($$X' UNION ALL SELECT usename FROM pg_user WHERE '' = '$$);
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- (0 rows)
Альтернативный способ исключить внедрение SQL-кода - пользоваться экранирующими функциями quote_ident, quote_literal, quote_nullable или format (см. раздаточный материал по типам данных и функциям).
Обычная команда SELECT получает сразу все строки:
=> SELECT * FROM authors;
author_id | last_name | first_name | middle_name -----------+------------+------------+------------- 1 | Пушкин | Александр | Сергеевич 2 | Тургенев | Иван | Сергеевич 3 | Стругацкий | Борис | Натанович 4 | Стругацкий | Аркадий | Натанович 6 | Свифт | Джонатан | 5 | Толстой | Лев | Николаевич (6 rows)
Курсор позволяет получать данные построчно.
=> BEGIN;
BEGIN
=> DECLARE c CURSOR FOR SELECT * FROM authors;
DECLARE CURSOR
=> FETCH c;
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- 1 | Пушкин | Александр | Сергеевич (1 row)
=> FETCH c;
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- 2 | Тургенев | Иван | Сергеевич (1 row)
Размер выборки можно указывать:
=> FETCH 2 c;
author_id | last_name | first_name | middle_name -----------+------------+------------+------------- 3 | Стругацкий | Борис | Натанович 4 | Стругацкий | Аркадий | Натанович (2 rows)
Размер выборки играет большое значение, когда строк очень много: обрабатывать большой объем данных построчно очень неэффективно. Мы проверим этот факт при выполнении практического задания.
Если в процессе чтения мы дойдем до конца таблицы, FETCH просто перестанет возвращать строки. В обычных языках программирования всегда есть возможность проверить это условие.
=> FETCH 2 c;
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- 6 | Свифт | Джонатан | 5 | Толстой | Лев | Николаевич (2 rows)
=> FETCH 2 c;
author_id | last_name | first_name | middle_name -----------+-----------+------------+------------- (0 rows)
=> CLOSE c;
CLOSE CURSOR
=> COMMIT;
COMMIT
Конец демонстрации.