Транзакции

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

Когда использовать транзакции

  • При сложных изменениях данных, которые состоят из нескольких операций.

  • Когда важно, чтобы другие процессы не видели промежуточные результаты.

Как работать с транзакциями

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

Используйте объект 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 откатит все изменения,

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

  • если вы решили откатить и сами находитесь во вложенной транзакции — процесс повторится на уровень выше.

Избегайте откатов во вложенных транзакциях. Это усложняет логику. Условия отката должен определять код основной транзакции.

Как обрабатывать ошибки во вложенных транзакциях

Если вложенная транзакция завершилась с ошибкой:

  1. откатите основную транзакцию полностью — rollbackTransaction,

  2. решите, как следует обработать ошибку — залогировать, повторить операцию или выполнить другие действия.

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;
}
Следующая