Транзакции в PostgreSQL версии 8.0
Автор: Джошуа Д. Дрейк
Несомненно, транзакции очень хороши, но транзакции в
предыдущих версиях PostgreSQL пропагандировали лозунг – “все, или ничего”,
останавливая транзакцию, если ошибка произошла в ее пределах. К счастью,
новая версия PostgreSQL 8 позволяет взглянуть на это подругому, добавляя
“savepoints” (точки сохранения), позволяя откатить назад только часть
транзакции и восстановиться от ошибки изящно.
Одна из очень хороших особенностей PostgreSQL –
транзакции. Они предотвращают случайную потерю данных или их искажение.
Например, скажем, вы хотите удалить записи в таблице. В
PostgreSQL команда выглядит так:
template1=# DELETE FROM
foo;
Однако, данная команда удаляет все записи в таблице. Это,
вероятно, вовсе не то, чего вы хотите, и, если вы не использовали
транзакцию, восстановление из резервной копии будет единственным способом
восстановить базу. Используя транзакции, вернуть данные очень просто:
BEGIN;
DELETE FROM foo;
DELETE 50
Параметр BEGIN заставляет базу начать транзакцию.
Поскольку, скоро вы понимаете, что забыли включить параметр WHERE, и
удалили все столбцы, то вам необходимо будет сделать обратную перемотку и
восстановить данные:
BEGIN;
DELETE FROM foo;
DELETE 50
ROLLBACK;
Есть один недостаток в такой реализации транзакций – если
внутри транзакции произошла ошибка, вы вынуждены сделать перемотку.
Команда перемотки должна будет выполнена в пределах транзакции, до того,
как произведены какие-либо команды в пределах соединения. Как только
перемотка будет выполнена, вы должны будете повторить действия снова в том
виде, который не будет вызывать ошибку. Это правило включает в себя и
ошибки пользователей: типа удаления всех отчетов в таблице; синтаксические
ошибки: типа попытки выбрать из столбца, который не существует. Например:
BEGIN;
UPDATE foo SET bar =
(SELECT count(*) FROM baz));
INSERT INTO foo (column1)
SELECT column2 FROM bar;
ОШИБКА: отношение "bar" не
существует
CREATE TABLE bar (column1
text, column2 float);
ОШИБКА: текущая транзакция
прервана
команды, игнорируемые до
конца блока
Из-за ошибки вы будете делать перемотку и вся ваша
текущая работа будет потеряна. Этот специфический момент транзакций
PostgreSQL особенно раздражает в период отладки и тестирования.
Точки сохранения – путь к спасению
Новая версия PostgreSQL 8 обращается к этой проблеме с
помощью точек сохранения - 'savepoints'. Точки сохранения - это
именованные метки, которые разбивают транзакцию на этапы, заставляя базу
сохранять данные в рамках каждого этапа. В случае ошибки, мы можем
определить этап на котором она возникла, и сделать перемотку только до
предыдущей точки сохранения, не теряя при этом работу, которая лежит перед
savepoint с ошибкой. Поскольку кажная savepoint имеет свое имя, то и
обращаться к ней необходимо по ее имени.
Чтобы инициализировать savepoint, вы должны быть в
пределах операционного блока:
template1=# BEGIN;
BEGIN
template1=# INSERT INTO
foo(column1,column2,column3) VALUES (1,2,0);
INSERT 17231 1
template1=# INSERT INTO
foo(column1,column2,column3) VALUES (1,2,0);
INSERT 17232 1
template1=# INSERT INTO
foo(column1,column2,column3) VALUES (1,2,0);
INSERT 17233 1
template1=# SELECT * FROM
foo;
column1 | column2 | column3
--------+---------+--------
1 | 2 | 0
1 | 2 | 0
1 | 2 | 0
(3 rows)
template1=# SAVEPOINT
main_values_inserted;
SAVEPOINT
template1=# INSERT INTO
foo(column1,column2,column3) VALUES (1,2,1/0);
ОШИБКА: деление на ноль
ОШИБКА: деление на ноль
В предыдущей версии PostgreSQL, вы потеряли бы все INSERT
данные в предыдущем коде (после возникновения ошибки деления на ноль в
последнем утверждении INSERT), но в версии 8 вы можете сделать перемотку
до определенного savepoint. Обратите внимание, что код содержит savepoint
с именем 'main_values_inserted'. Для обратной перемотки до этой savepoint
вы можете написать:
template1=# ROLLBACK TO
main_values_inserted;
ROLLBACK
Выполняя перемотку до этой savepoint вы сохраняете все,
что было до нее, теряя только работу выполненную после savepoint. После
перемотки вы можете продолжить свою работу без полного повтора транзакции:
template1=# INSERT INTO
foo(column1,column2,column3) VALUES (5,9,10);
INSERT 17234 1
template1=# INSERT INTO
foo(column1,column2,column3) VALUES (5,9,10);
INSERT 17235 1
template1=# INSERT INTO
foo(column1,column2,column3) VALUES (5,9,10);
INSERT 17236 1
template1=# INSERT INTO
foo(column1,column2,column3) VALUES (5,9,10);
INSERT 17237 1
template1=# SAVEPOINT
secondary_values_inserted;
SAVEPOINT
template1=# SELECT * FROM
foo;
column1 | column2 | column3
--------+---------+--------
1 | 2 |
0
1 | 2 |
0
1
| 2 | 0
5 | 9
| 10
5 | 9 |
10
5 | 9 |
10
5 | 9 |
10
(7 rows)
template1=# SAVEPOINT
all_values_inserted;
SAVEPOINT
template1=# DELETE FROM
foo;
DELETE 7
template1=# SELECT * FROM
foo;
column1 | column2 | column3
---------+--------+--------
(0 rows)
Ой. Также, как и в первом примере этой статьи, вы
фактически хотели удалить только одну строку где 'column = 1'. Но вы
можете сделать перемотку ко второй savepoint 'all_values_inserted' в коде
выше.
template1=# ROLLBACK TO
all_values_inserted;
ROLLBACK
template1=# SELECT * FROM
foo;
column1 | column2 | column3
--------+---------+--------
1 | 2 |
0
1
| 2 | 0
1 | 2
| 0
5 | 9
| 10
5 | 9
| 10
5 | 9
| 10
5 | 9
| 10
(7 rows)
Обратите внимание, это восстановило все ваши данные.
Теперь вы можете запустить корректные команды для удаления.
template1=# DELETE FROM foo
WHERE column1 = 1;
DELETE 3
template1=# SELECT * FROM
foo;
column1 | column2 | column3
--------+---------+--------
5
| 9 | 10
5 | 9 |
10
5 | 9
| 10
5 | 9
| 10
(4 rows)
Наконец, вы можете завершить вашу транзакцию. Последняя
команда SELECT показывает, что целостность данных после всех этих вставок
и обратных перемоток не повреждена.
template1=# COMMIT;
COMMIT
template1=# select * from
foo;
column1 | column2 | column3
--------+---------+--------
5 | 9
| 10
5 | 9
| 10
5 | 9
| 10
5 | 9
| 10
(4 rows)
Джошуа Д. Дрейк – Президент Command Prompt, Inc. Компания
занимается поддержкой PostgreSQL программированием. Он также соавтор
'Практического PostgreSQL' от издательства O'reilly и Партнеров.
Оригинал на http://www.devx.com/dbzone/Article/22119
Перевод Феськов
Кузьма