Транзакции
Транзакция — это набор операций с базой данных, которые выполняются как единое целое: либо выполняются все операции, либо не выполняется ни одной. Это гарантирует целостность данных, даже если несколько процессов работают с базой одновременно.
Когда использовать транзакции
-
При сложных изменениях данных, которые состоят из нескольких операций.
-
Когда важно, чтобы другие процессы не видели промежуточные результаты.
Как работать с транзакциями
Внимание
Делайте транзакции короткими. Долгие транзакции увеличивают риск взаимных блокировок, каскадных откатов и аварийного завершения операций.
Используйте объект 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;
}