LINUX.ORG.RU

ROLLBACK транзакций в цикле - как правильно делать?

 , rollback, savepoint


1

1

Скрипт написан на PHP.

В нем есть PHP-цикл, который выполняет порцию из ~1000 отдельных INSERT. Отдельные INSERT (а не один большой) сделаны вот по какой причине: некоторые INSERT могут завершиться ошибкой, например если значение вставляемого поля не уникально для поля с флагом UNIQUE (у меня это поле содержит uuid). Такие ошибки надо просто проигнорировать.

После этих INSERT-ов, в другую, сервисную, таблицу записывается последний корретно добавленый uuid. Это нужно из-за того, что данные для очередной порции INSERT могут вытягиваться с разных хостов, и для каждого такого хоста хранится свой uuid.

В любом случае, 1000 INSERT-ов и команда UPDATE для сохранения uuid, должны оборачиваться в одну транзакцию. Это нужно для сохранения консистенции данных. Проблема в том, что если при выполнении какого-то INSERT совпадет uuid, то будет ошибка, и будет откатана ВСЯ транзакция.

Чтобы это обойти, я хочу воспользоваться SAVEPOINT. То есть, сделать перед INSERT команду SAVEPOINT, и если INSERT завершился ошибкой, сделать ROLLBACK. Но я читаю документацию, и не могу понять, как это правильно сделать. У меня есть два варианта:

Вариант первый - простой:

BEGIN;

цикл на ~1000

 SAVEPOINT beforeInsert;
 INSERT ...
 если неудачно
   ROLLBACK TO SAVEPOINT beforeInsert;

конец цикла

UPDATE last_uuid ...;
COMMIT;


Вариант второй - каждый раз удалять SAVEPOINT:
BEGIN;

цикл на ~1000

 SAVEPOINT beforeInsert;
 INSERT ...
 если неудачно
   ROLLBACK TO SAVEPOINT beforeInsert;

 RELEASE SAVEPOINT beforeInsert

конец цикла

UPDATE last_uuid ...;
COMMIT;


Тут есть две непонятки.

Непонятка первая:

Вроде как написано, что не может быть несколько точек сохранения под одним и тем же именем, поэтому удаление SAVEPOINT в цикле - необходимо. С другой стороны, SAVEPOINT с одним и тем же именем должен быть _один_ в пределах одной транзакции, а мы в цикле таки создаем SAVEPOINT с одним и тем же именем. То есть тут вопрос, а можем ли мы в принципе создавать SAVEPOINT в пределах одной транзакции с одним и тем же именем, даже если мы его и удаляем, или надо все время генерить SAVEPOINT с разными именами?


Непонятка вторая:

Я пока что склоняюсь, что вариант кода 2 должен быть рабочим. Но хотелось бы узнать, что лучше по производительности: постоянно создавать и удалять SAVEPOINT, или создавать SAVEPOINT с разными именами (например beforeInsert0, beforeInsert1, ... beforeInsertN), и они потом сами удалятся при завершении транзакции?

★★★★★

если при выполнении какого-то INSERT совпадет uuid, то будет ошибка, и будет откатана ВСЯ транзакция.

Кем будет откатана и почему?

Legioner ★★★★★
()

А это не снимает вопросы?

Compatibility
SQL requires a savepoint to be destroyed automatically when another savepoint with the same name is established. In PostgreSQL, the old savepoint is kept, though only the more recent one will be used when rolling back or releasing. (Releasing the newer savepoint with RELEASE SAVEPOINT will cause the older one to again become accessible to ROLLBACK TO SAVEPOINT and RELEASE SAVEPOINT.) Otherwise, SAVEPOINT is fully SQL conforming.

https://www.postgresql.org/docs/9.1/sql-savepoint.html

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от ya-betmen

SQL requires a savepoint to be destroyed automatically when another savepoint with the same name is established. In PostgreSQL, the old savepoint is kept, though only the more recent one will be used when rolling back or releasing. (Releasing the newer savepoint with RELEASE SAVEPOINT will cause the older one to again become accessible to ROLLBACK TO SAVEPOINT and RELEASE SAVEPOINT.) Otherwise, SAVEPOINT is fully SQL conforming.

Получается, что правильным вариантом будет вариант второй из топика. Потому что если не высвобождать точку сохранения на каждой итерации цикла, точки сохранения будут копиться (до коммита транзакции).

Тут надо понять, что сильнее дает нагрузку на систему: постоянное выделение памяти под каждую точку сохранения, или выжирание процессорного времени на очистку точки сохранения при каждой итерации. Что можешь сказать по этому вопросу?

Xintrea ★★★★★
() автор топика
Последнее исправление: Xintrea (всего исправлений: 1)
Ответ на: комментарий от Legioner

Кем будет откатана и почему?

Транзакция откатывается, если при ее выполнении хотя бы один SQL-запрос завершился ошибкой.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Предполагаю, что на 1000 записей ты вряд ли заметишь разницу, если конечно у тебя не лежит томик войны и мира в каждой.

ya-betmen ★★★★★
()
Ответ на: комментарий от Xintrea

Как я и сказал: не бывает «не могу обновиться». Бывает «не хочу потому что бюрократия», а это не то же самое.

AnDoR ★★★★★
()
Ответ на: комментарий от Xintrea

Транзакция откатывается, если при ее выполнении хотя бы один SQL-запрос завершился ошибкой.

Это не так.

Legioner ★★★★★
()
Ответ на: комментарий от AnDoR

Бывает «не хочу потому что бюрократия», а это не то же самое.

Еще бывает, что «невозможно, потому что повторное прохождение сертификации не заложено в проект».

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Расскажи нам как, на PostgreSQL 9.1 без «on conflict do nothing» и без точек сохранения.

Оно просто так работает. Могу на 9.2 протестировать, хз, где 9.1 взять, даже на официальном сайте уже нет его. На последнем постгресе утром тестировал, проблем нет. Но я тебе и так скажу, что ни одна база не будет откатывать транзакцию из-за ошибки.

Возьми psql и сам проверь.

Legioner ★★★★★
()
Последнее исправление: Legioner (всего исправлений: 2)
Ответ на: комментарий от Legioner

насколько помню, можно и вложенные транзакции делать, это решит костыль с ошибочным инсертом, в цикле стартовать вложенную транзу и откатывать ее в случае ошибки, причем там же в цикле вставить и тот неуникальный ууид, который ошибку и вызывал. не?

deep-purple ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.