Представьте ситуацию: вам нужно списать деньги с одного счета и зачислить их на другой.
Это две отдельные SQL-операции (UPDATE).
Что, если первая выполнится успешно, а вторая — с ошибкой? Деньги «повиснут в воздухе».
Чтобы предотвратить такие проблемы, существуют транзакции.
Транзакция — это механизм базы данных, который гарантирует, что группа из нескольких SQL-операций будет выполнена по принципу «всё или ничего».
Либо все операции в группе успешно завершаются, либо все они полностью отменяются.
Когда нужно использовать транзакции?
- Когда операция состоит из двух и более взаимосвязанных запросов на изменение данных (INSERT, UPDATE, DELETE).
- При проведении заказов (списание со склада, создание оплаты).
- При сложных операциях импорта данных.
- При любых действиях, где частичное выполнение недопустимо.
Как работать с транзакциями в Битрикс D7
Современный способ — использовать методы объекта соединения D7.
Основной шаблон:
use Bitrix\Main\Application;
use Bitrix\Main\DB\Connection;
/** @var Connection $connection */
$connection = Application::getConnection();
try {
// 1. Начинаем транзакцию
$connection->startTransaction();
// --- Выполняем группу операций ---
// Например, обновление элемента ORM
$result1 = \My\Table::update(1, ['QUANTITY' => 5]);
if (!$result1->isSuccess()) {
// Если первая операция не удалась, выбрасываем исключение
throw new \Exception(implode(', ', $result1->getErrorMessages()));
}
// И еще одна операция
$result2 = \My\AnotherTable::add(['ITEM_ID' => 1, 'STATUS' => 'PROCESSED']);
if (!$result2->isSuccess()) {
// Если вторая операция не удалась, выбрасываем исключение
throw new \Exception(implode(', ', $result2->getErrorMessages()));
}
// ---------------------------------
// 2. Если все прошло успешно, "коммитим" транзакцию
$connection->commitTransaction();
echo "Все операции успешно выполнены!";
} catch (\Exception $e) {
// 3. Если на любом этапе возникло исключение, "откатываем" транзакцию
$connection->rollbackTransaction();
echo "Произошла ошибка, все изменения отменены: " . $e->getMessage();
}Разбор шагов:
- $connection->startTransaction(): Подает базе данных команду начать новую транзакцию. Все последующие SQL-запросы будут выполняться «виртуально» в рамках этой транзакции.
- Блок try: Здесь размещается вся ваша бизнес-логика. Важно проверять результат каждой операции и в случае неудачи немедленно прерывать выполнение, выбрасывая исключение.
- $connection->commitTransaction(): Если код дошел до этой строки, значит, все операции в блоке try прошли без ошибок. Эта команда говорит базе данных: «Все в порядке, сохрани все изменения, сделанные с начала транзакции».
- Блок catch и $connection->rollbackTransaction(): Если в блоке try было выброшено исключение, выполнение переходит в catch. Команда rollbackTransaction говорит базе данных: «Отмени все изменения, сделанные с начала этой транзакции».
Старый API ($DB)
Аналогичные методы есть и у глобального объекта $DB:
- $DB->StartTransaction()
- $DB->Commit()
- $DB->Rollback()
Логика их использования точно такая же.
Вложенные транзакции
D7 ORM поддерживает вложенные транзакции. Если вы вызовете startTransaction() внутри уже начатой транзакции, Битрикс создаст «точку сохранения» (SQL SAVEPOINT). commitTransaction() во вложенной транзакции ничего не сделает, а rollbackTransaction() откатит изменения только до этой точки сохранения. Это сложный механизм, и в 99% случаев лучше избегать вложенных транзакций, выстраивая логику линейно.
| Действие | Поведение |
|---|---|
startTransaction() внутри другой | Создаёт точку сохранения |
commitTransaction() вложенной | Не сохраняет в БД, ждёт внешнего коммита |
rollbackTransaction() вложенной | Откат до SAVEPOINT, выбрасывает TransactionException |
Рекомендация: избегайте вложенных откатов. Управление ошибками должно быть в основной транзакции.
Обработка ошибок во вложенных транзакциях
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();
// Логика обновления
$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;
}
Практические советы
- Делайте транзакции короткими — это снижает риск блокировок
- Проверяйте результат каждой операции (
isSuccess()) - Логируйте ошибки и исключения
- Не смешивайте ORM и SQL без крайней необходимости
- Если используете
DataManager::getConnectionName, убедитесь, что транзакция охватывает нужное соединение
Архитектурные нюансы
- Вложенные транзакции — мощный инструмент, но требуют строгой дисциплины
- Ошибки во вложенной транзакции могут не остановить внешнюю — это опасно
- Лучше использовать линейную логику и централизованную обработку ошибок
Вывод:
Транзакции — это обязательный инструмент для написания надежного кода, который изменяет данные.
Всегда оборачивайте группы взаимосвязанных add, update или delete операций в блок try…catch с startTransaction и commit/rollback.
Это единственный способ гарантировать целостность и консистентность вашей базы данных.
транзакции Битрикс, startTransaction, commitTransaction, rollbackTransaction, целостность данных, D7, Application::getConnection, SQL.