Транзакции
Транзакция — это набор операций с базой данных, которые выполняются как единое целое: либо выполняются все операции, либо не выполняется ни одной. Это гарантирует целостность данных, даже если несколько процессов работают с базой одновременно.
Когда использовать транзакции
-
При сложных изменениях данных, которые состоят из нескольких операций.
-
Когда важно, чтобы другие процессы не видели промежуточные результаты.
Как работать с транзакциями
Делайте транзакции короткими. Долгие транзакции увеличивают риск взаимных блокировок, каскадных откатов и аварийного завершения операций.
Используйте объект Bitrix\Main\DB\Connection. Внутри транзакции можно выполнять SQL-запросы или работать с ORM:
$db = \Bitrix\Main\Application::getConnection();
try {
$db->startTransaction();
// Пример SQL-запроса
$db->queryExecute(
"UPDATE my_table SET active = '".\$db->getSqlHelper()->forSql('N')."' WHERE age > ".(int)0
);
// Пример изменения через ORM
\Bitrix\Main\SiteTable::update('s1', ['ACTIVE' => 'N']);
$db->commitTransaction();
} catch (Throwable $e) {
$db->rollbackTransaction();
throw $e;
}
-
startTransaction()— начинает новую транзакцию. Все последующие операции будут частью этой транзакции до ее завершения. -
commitTransaction()— подтверждает транзакцию, применяя все изменения в базе данных. Если транзакция успешна, данные сохраняются. -
rollbackTransaction()— отменяет транзакцию, отменяя все изменения, сделанные в ее рамках. Используется при ошибках для возврата в исходное состояние.
Если ORM-объект переопределяет соединение через DataManager::getConnectionName, то транзакция будет работать только в рамках этого соединения.
Особенности вложенных транзакций
Можно вызывать транзакции внутри других транзакций. В этом случае:
-
startTransaction()внутри другой транзакции создает точку сохранения —SAVEPOINT, -
commitTransaction()во вложенной транзакции ничего не сохраняет в БД — сохранение происходит при коммите внешней транзакции, -
rollbackTransaction()делает частичный откат до точки сохранения и выбрасывает исключениеTransactionException.
Что может произойти дальше:
-
если исключение не поймано — скрипт упадет, MySQL откатит все изменения,
-
если вы поймали исключение и решили продолжить — изменения из внешней транзакции могут быть сохранены,
-
если вы решили откатить и сами находитесь во вложенной транзакции — процесс повторится на уровень выше.
Избегайте откатов во вложенных транзакциях. Это усложняет логику. Условия отката должен определять код основной транзакции.
Как обрабатывать ошибки во вложенных транзакциях
Если вложенная транзакция завершилась с ошибкой:
-
откатите основную транзакцию полностью —
rollbackTransaction, -
решите, как следует обработать ошибку — залогировать, повторить операцию или выполнить другие действия.
use Bitrix\Main\Application;
use Bitrix\Main\DB\Connection;
use Bitrix\Main\DB\SqlExpression;
use Bitrix\Main\DB\TransactionException;
// Функция обновления счетов
function updateAccounts(int $userId, Connection $db) {
try {
$db->startTransaction();
// Ваш код изменения данных
$db->commitTransaction();
} catch (Throwable $e) {
$db->rollbackTransaction();
throw $e;
}
}
// Функция обновления заказов
function updateOrders(int $userId, Connection $db) {
try {
$db->startTransaction();
// Ваш SQL-запрос
$db->commitTransaction();
} catch (Throwable $e) {
$db->rollbackTransaction();
throw $e;
}
}
// Основная транзакция
$db = Application::getConnection();
try {
$db->startTransaction();
updateOrders($userId, $db); // Вложенная транзакция
updateAccounts($userId, $db); // Вложенная транзакция
$db->commitTransaction();
} catch (TransactionException $e) {
// Если ошибка во вложенной транзакции
$db->rollbackTransaction(); // Откатываем все
} catch (Throwable $e) {
$db->rollbackTransaction();
throw $e;
}