Дата последнего изменения: 21.09.2022
"Вложенные" транзакции доступны с версии main 22.200.0.
Mysql в движке InnoDB отвечает требованиям ACID к обработке транзакций (в MyISAM не поддерживается):
Нашему приложению транзакции могут помочь в обеспечении целостности данных, когда данные меняются несколькими операциями, и в обеспечении изолированности, чтобы конкурентные хиты не читали незакомиченные промежуточные данные.
Вот классический случай, когда проблема может/должна быть решена транзакцией:
При отправке писем методом CEvent::Send файл может быть не отправлен, если в момент выполнения скрипта будет запущен крон, который отправляет письма. Это происходит потому, что в функции /bitrix/modules/main/lib/mail/event.php
запись в таблицу b_event происходит до сохранения файла (58 строка и далее по условию).
Ранее, до обновления main 22.200.0, действовало правило:
Такое правило было потому, что Mysql не поддерживает вложенные транзакции. Могла сложиться ситуация, когда конечный сценарий открывает транзакцию, вызываемый API тоже открывает свою транзакцию, в итоге все коммиты и роллбеки перемешиваются непредсказуемым образом.
Сейчас в ядре появилась поддержка вложенных транзакций на уровне приложения (то есть при вызове соответствующего API, а не прямых запросов).
Для транзакций нужно использовать API драйвера БД \Bitrix\Main\DB\Connection
, методы startTransaction(), commitTransaction() и rollbackTransaction().
Если транзакции вложенные, то повторные старты транзакций создают именованные точки сохранения MySQL SAVEPOINT
. Промежуточные коммиты ничего не коммитят. Последний закрывающий коммит коммитит все произведенные с начала первой транзакции изменения.
Сложности возникают, если какие-то из вложенных транзакций захотят откатить изменения в своей транзакции. В этом случае мы попадаем в неопределенную ситуацию, т.к. итоговую цель всех изменений знает только конечный сценарий (см. старое правило).
Поэтому мы действуем в парадигме "вложенные роллбеки не поддерживаются" и даем возможность конечному сценарию решить, как поступить правильно. При наступлении события вложенного роллбека происходит частичный откат к соответствующей точке сохранения ROLLBACK TO SAVEPOINT ...
и выбрасывается исключение \Bitrix\Main\DB\TransactionException
.
Далее возможны три сценария:
Пример кода:
$conn = \Bitrix\Main\Application::getConnection(); $conn->query("truncate table test"); try { $conn->startTransaction(); $conn->query("insert into test values (1, 'one')"); // nested transaction $conn->startTransaction(); $conn->query("insert into test values (2, 'two')"); if (true) { $conn->commitTransaction(); } else { $conn->rollbackTransaction(); } // end of nested transaction $conn->commitTransaction(); } catch(\Bitrix\Main\DB\TransactionException $e) { $conn->rollbackTransaction(); }
В итоге новое правило работы с транзакциями выглядит так:
Не стоит также забывать, что транзакции не должны быть сильно протяженными по времени, иначе можно легко попасть на deadlock транзакций вплоть до полной остановки приложения. Могут наблюдаться бесконечные откаты транзакций, если операция не может быть завершена до прекращения скрипта по тайм-ауту.