В практике разработчика 1С часто встречается парадоксальная ситуация: программный код проверяет наличие активной транзакции с помощью функции ТранзакцияАктивна(), получает подтверждение (Истина), но при попытке выполнить метод ЗафиксироватьТранзакцию() система выдает исключение «Транзакция не активна». На первый взгляд это кажется программным сбоем платформы, однако за этим поведением стоит строгая логика механизмов управления данными. Разберем детально, почему так происходит и как правильно обрабатывать подобные ситуации.
Проанализируем работу платформы 1С:Предприятие. Метод ТранзакцияАктивна() работает достаточно просто: он проверяет внутренний счетчик уровня вложенности транзакций. Каждый раз, когда мы вызываем НачатьТранзакцию(), этот счетчик увеличивается на единицу. Каждый раз, когда вызывается ЗафиксироватьТранзакцию() или ОтменитьТранзакцию(), счетчик уменьшается.
Рассмотрим ситуацию: если счетчик больше нуля, ТранзакцияАктивна() вернет Истина. Но это лишь формальный признак того, что мы находимся внутри транзакционного блока. Это не гарантирует «здоровье» самой транзакции на уровне системы управления базами данных (СУБД).
Выясним причину самого частого сценария возникновения ошибки. Это происходит, когда внутри транзакционного блока возникла ошибка, которая была перехвачена конструкцией Попытка...Исключение.
НачатьТранзакцию();
Попытка
// Здесь происходит ошибка, например, нарушение уникальности индекса
НовыйОбъект.Записать();
Исключение
// Мы поймали ошибку и решили продолжить выполнение
ЗаписатьЛогОШибки("Ошибка записи");
КонецПопытки;
Если ТранзакцияАктивна() Тогда
// Метод вернет Истина, так как НачатьТранзакцию() был вызван
ЗафиксироватьТранзакцию(); // ОШИБКА: Транзакция не активна (или уже была отменена)
КонецЕсли;
В данном случае, как только внутри транзакции происходит любое исключение (даже если оно обработано в блоке Исключение), платформа помечает всю транзакцию как невалидную. Она становится «обреченной на откат». На уровне СУБД транзакция уже фактически прекращена или заблокирована, и любая попытка зафиксировать изменения (даже те, что были сделаны до ошибки) приведет к краху программы. Метод ТранзакцияАктивна() все еще видит «открытую скобку» начала транзакции, но зафиксировать её уже невозможно. Чтобы найти такие ошибки заранее, стоит применять статический анализатор кода.
Разберем еще один важный аспект — неявные транзакции, инициируемые самой платформой. Проанализируем работу системы при записи любого объекта (справочника или документа). Когда мы нажимаем кнопку «Записать», платформа автоматически открывает транзакцию.
Внутри обработчиков событий, таких как ПередЗаписью, ПриЗаписи или ОбработкаПотребления, функция ТранзакцияАктивна() всегда будет возвращать Истина. Однако попытка вызвать ЗафиксироватьТранзакцию() вручную внутри этих процедур приведет к ошибке. Посмотрим на логику системы: платформенная транзакция управляется самой платформой. Вы не можете «закрыть» её раньше времени, так как это нарушит целостность процесса записи, который должен завершиться только после выполнения всех фоновых механизмов (индексации, проверки и т.д.).
Согласно стандартам разработки 1С, вызовы методов НачатьТранзакцию() и ЗафиксироватьТранзакцию() (или ОтменитьТранзакцию()) должны располагаться в контексте одного и того же метода. Рассмотрим, к чему ведут отклонения от этого правила. Если вы начали транзакцию в одной процедуре, а пытаетесь завершить ее в другой, которая вызывается из первой, это часто приводит к путанице в уровнях вложенности.
Если во внешней процедуре транзакция была прервана из-за таймаута или критической ошибки СУБД, вложенная процедура может все еще «считать», что находится в транзакции, но физическое соединение или контекст в базе данных уже утрачены.
Чтобы избежать подобных ошибок, рекомендуется следовать проверенному алгоритму:
Попытка...Исключение, в блоке Исключение обязательно должен быть вызван метод ОтменитьТранзакцию().ТранзакцияАктивна() проверяет только факт наличия открытых операторов, но не состояние данных в СУБД.Проанализируем эталонный шаблон кода для работы с транзакциями:
Процедура ВыполнитьДействие()
НачатьТранзакцию();
Попытка
// Выполняем действия с данными
ВыполнитьВажнуюЗапись();
// Если дошли до этой строки, значит ошибок не было
ЗафиксироватьТранзакцию();
Исключение
// В случае любой ошибки откатываем все изменения
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
// Сообщаем об ошибке пользователю или записываем в журнал
ЗаписатьЛог(ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
В этом примере мы гарантируем, что если произойдет сбой, транзакция будет корректно закрыта методом ОтменитьТранзакцию(), что сбросит счетчик и приведет состояние системы в соответствие с реальностью. При следующей проверке ТранзакцияАктивна() вернет Ложь, и мы избежим попытки фиксации «битых» данных.
Иногда ошибка «Транзакция не активна» возникает из-за факторов, не зависящих от кода 1С:
Подводя итог, отметим: появление данной ошибки — это всегда сигнал о том, что логическая последовательность действий с базой данных была нарушена ранее. Для исправления ситуации необходимо найти место, где возникло первое исключение, и убедиться, что оно корректно обрабатывается вызовом ОтменитьТранзакцию().