В очередной раз встретился с необходимостью использовать переменную утилиты psql, которая называется ON_ERROR_ROLLBACK. В прошлом году рассказывал про переменную ECHO_HIDDEN, настала пора на следующую посмотреть.
Это переменная утилиты psql, только в ней её можно установить.
- \set ON_ERROR_ROLLBACK OFF (по умолчанию)
- Демонстрация \set ON_ERROR_ROLLBACK ON
- Как работает ON_ERROR_ROLLBACK
- Эксперимент 1 (\set ON_ERROR_ROLLBACK OFF)
- Эксперимент 2 (\set ON_ERROR_ROLLBACK ON)
- Эксперимент 3 (\set ON_ERROR_ROLLBACK INTERACTIVE)
- Журнал сообщений сервера
- Исходный код
\set ON_ERROR_ROLLBACK OFF (по умолчанию)
Когда мы начинаем транзакцию в psql, внутри неё не нужно совершать ошибок-опечаток, иначе мы не сможем продолжить работу, транзакция оборвется и придётся начинать всё заново. Давай проиллюстрируем:
Для начала — создадим таблицу для экспериментов:
CREATE TABLE OER (id integer);
И выполним несколько команд:
student=# BEGIN;
BEGIN
student=*# SELECT n;
ERROR: column "n" does not exist
LINE 1: SELECT n;
^
student=!# SELECT 1;
ERROR: current transaction is aborted, commands ignored until end of transaction block
student=!# ROLLBACK;
ROLLBACK
Обратите внимание на символы звездочка, восклицательный знак в окончании приглашения psql. Они помогают понять что к чему. Восклицательный знак означает что транзакция уже оборвана и продолжать нельзя.
Поведение это не всегда удобно, хотелось бы продолжать действия внутри транзакции даже после опечатки.
\set ON_ERROR_ROLLBACK ON
Это можно сделать установив переменную psql ON_ERROR_ROLLBACK следующим образом:
\set ON_ERROR_ROLLBACK ON
Повторим пример выше:
student=# BEGIN;
BEGIN
student=*# SELECT n;
ERROR: column "n" does not exist
LINE 1: SELECT n;
^
student=*# SELECT 1;
?column?
----------
1
(1 row)
student=*# ROLLBACK;
ROLLBACK
Делаем опечатки, но продолжаем работу внутри транзакции. Отлично!
Как работает ON_ERROR_ROLLBACK
За счет чего происходит такая работа? В документации на утилиту psql сказано, что, при включенном ON_ERROR_ROLLBACK неявно выполняется команда SAVEPOINT непосредственно перед каждой командой в блоке транзакции, а в случае ошибки команды происходит откат к этой точке сохранения.
Вроде всё понятно, но я бы хотел акцентировать внимание вот на чём — чтобы можно было откатывать в рамках текущей транзакции, в ней создаётся несколько вложенных транзакций (subtransaction), которыми мы далее управляем. И вложенные транзакции имеют свой собственный номер. Он больше, чем номер основной транзакции. Вы можете это всё проверить самостоятельно, хороший пример есть в книге Егора Рогова «PostgreSQL 15 изнутри», раздел «3.7. Вложенные транзакции».
Но помните, что txid_current() показывает номер основной транзакции, но не вложенных. А номер вложенных будет отличаться от основной! Так что полагаться на txid_current() при работе с вложенными транзакциями может быть опасно.
Эксперимент 1 (\set ON_ERROR_ROLLBACK OFF)
Для иллюстрации выполним ещё пару экспериментов. Выключим эту переменную:
\set ON_ERROR_ROLLBACK OFF
И выполним вот такие команды (в моём psql строка приглашения имеет вид #. Да, просто шарп).
BEGIN; SELECT txid_current(); INSERT INTO oer VALUES (1),(2),(3); SELECT *, xmin FROM oer; SELECT txid_current(); INSERT INTO oer VALUES (11),(22),(33); SELECT *, xmin FROM oer; SELECT txid_current(); ROLLBACK;
Команды с результатом:
# BEGIN;
BEGIN
# SELECT txid_current();
txid_current
--------------
3098
(1 row)
# INSERT INTO oer VALUES (1),(2),(3);
INSERT 0 3
# SELECT *, xmin FROM oer;
id | xmin
----+------
1 | 3098
2 | 3098
3 | 3098
(3 rows)
# SELECT txid_current();
txid_current
--------------
3098
(1 row)
# INSERT INTO oer VALUES (11),(22),(33);
INSERT 0 3
# SELECT *, xmin FROM oer;
id | xmin
----+------
1 | 3098
2 | 3098
3 | 3098
11 | 3098
22 | 3098
33 | 3098
(6 rows)
# SELECT txid_current();
txid_current
--------------
3098
(1 row)
# ROLLBACK;
ROLLBACK
Эксперимент 2 (\set ON_ERROR_ROLLBACK ON)
А теперь опять включим переменную…:
\set ON_ERROR_ROLLBACK ON
… и повторим тот же эксперимент, только добавим ошибку в середине — чтобы показать что транзакция успешно продолжает выполнение.
BEGIN; SELECT txid_current(); INSERT INTO oer VALUES (1),(2),(3); SELECT *, xmin FROM oer; SELECT n; SELECT txid_current(); INSERT INTO oer VALUES (11),(22),(33); SELECT *, xmin FROM oer; SELECT txid_current(); ROLLBACK;
Команды с результатом:
# BEGIN;
BEGIN
# SELECT txid_current();
txid_current
--------------
3100
(1 row)
# INSERT INTO oer VALUES (1),(2),(3);
INSERT 0 3
# SELECT *, xmin FROM oer;
id | xmin
----+------
1 | 3101
2 | 3101
3 | 3101
(3 rows)
# SELECT n;
ERROR: column "n" does not exist
LINE 1: SELECT n;
# SELECT txid_current();
txid_current
--------------
3100
(1 row)
# INSERT INTO oer VALUES (11),(22),(33);
INSERT 0 3
# SELECT *, xmin FROM oer;
id | xmin
----+------
1 | 3101
2 | 3101
3 | 3101
11 | 3103
22 | 3103
33 | 3103
(6 rows)
# SELECT txid_current();
txid_current
--------------
3100
(1 row)
# ROLLBACK;
ROLLBACK
Эксперимент 3 (\set ON_ERROR_ROLLBACK INTERACTIVE)
А теперь покажу что происходит с режимом INTERACTIVE. Включим этот режим работы:
\set ON_ERROR_ROLLBACK INTERACTIVE
И опять выполним в psql’e команды из второго эксперимента, там, где была ошибка в середине. Сейчас выполнение будет идентично, создаются вложенные транзакции.
А теперь создадим файл oer.sql и скопируем туда те же команды, из второго эксперимента.
BEGIN; SELECT txid_current(); INSERT INTO oer VALUES (1),(2),(3); SELECT *, xmin FROM oer; SELECT n; SELECT txid_current(); INSERT INTO oer VALUES (11),(22),(33); SELECT *, xmin FROM oer; SELECT txid_current(); ROLLBACK;
И выполним этот файл из psql с помощью метакоманды \i (либо \include). Эта команда читает и выполняет команды из переданного файла. И теперь результат будет следующим:
# \i oer.sql
BEGIN
txid_current
--------------
3126
(1 row)
INSERT 0 3
id | xmin
----+------
1 | 3126
2 | 3126
3 | 3126
(3 rows)
psql:oer.sql:5: ERROR: column "n" does not exist
LINE 1: SELECT n;
^
psql:oer.sql:6: ERROR: current transaction is aborted, commands ignored until end of transaction block
psql:oer.sql:7: ERROR: current transaction is aborted, commands ignored until end of transaction block
psql:oer.sql:8: ERROR: current transaction is aborted, commands ignored until end of transaction block
psql:oer.sql:9: ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK
Теперь мы видим ошибку, так как режим ON_ERROR_ROLLBACK INTERACTIVE. Его можно будет установить по умолчанию (в .psqlrc), но обязательно нужно помнить про особенность отображения номера текущей транзакции.
Журнал сообщений сервера
А теперь я покажу что неявная точка сохранения точно добавляется. Достаточно заглянуть в журнал сообщений сервера — там эта информация будет записана явно:
BEGIN; SAVEPOINT pg_psql_temporary_savepoint SELECT txid_current(); RELEASE pg_psql_temporary_savepoint SAVEPOINT pg_psql_temporary_savepoint INSERT INTO oer VALUES (1),(2),(3); RELEASE pg_psql_temporary_savepoint SAVEPOINT pg_psql_temporary_savepoint SELECT *, xmin FROM oer; RELEASE pg_psql_temporary_savepoint SAVEPOINT pg_psql_temporary_savepoint column "n" does not exist at character 8 SELECT n; ROLLBACK TO pg_psql_temporary_savepoint SAVEPOINT pg_psql_temporary_savepoint SELECT txid_current(); RELEASE pg_psql_temporary_savepoint SAVEPOINT pg_psql_temporary_savepoint INSERT INTO oer VALUES (11),(22),(33); RELEASE pg_psql_temporary_savepoint SAVEPOINT pg_psql_temporary_savepoint SELECT *, xmin FROM oer; RELEASE pg_psql_temporary_savepoint SAVEPOINT pg_psql_temporary_savepoint SELECT txid_current(); RELEASE pg_psql_temporary_savepoint SAVEPOINT pg_psql_temporary_savepoint ROLLBACK;
Перед каждой командой добавляется SAVEPOINT pg_psql_temporary_savepoint и после каждой команды происходит откат. Накладные расходы точно тут будут. Точки сохранения называются одинаково, но это и не принципиально — нам нужно откатиться только к предыдущему состоянию, а не куда-то ниже по стеку.
Исходный код
Ну и в самом конце — заглянем в исходный код (16_STABLE), в файл postgres/src/bin/psql/common.c:
if (transaction_status == PQTRANS_INTRANS &&
pset.on_error_rollback != PSQL_ERROR_ROLLBACK_OFF &&
(pset.cur_cmd_interactive ||
pset.on_error_rollback == PSQL_ERROR_ROLLBACK_ON))
{
PGresult *result;
result = PQexec(pset.db, "SAVEPOINT pg_psql_temporary_savepoint");
if (PQresultStatus(result) != PGRES_COMMAND_OK)
{
pg_log_info("%s", PQerrorMessage(pset.db));
ClearOrSaveResult(result);
goto sendquery_cleanup;
}
ClearOrSaveResult(result);
on_error_rollback_savepoint = true;
}
В условии, в том числе, проверяется и установка переменной ON_ERROR_ROLLBACK, далее добавляется точка сохранения («SAVEPOINT pg_psql_temporary_savepoint») и выполняется с помощью команды PQexec. Что мы и видим в логе. Ниже есть и кусочки про RELEASE pg_psql_temporary_savepoint и ROLLBACK TO pg_psql_temporary_savepoint.
Leave a Reply