Как правильно обработать исключение внутри транзакции в 1С и избежать ошибки «В данной транзакции уже происходили ошибки»?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Разберем одну из самых коварных проблем при разработке на платформе 1С:Предприятие 8 — ситуацию, когда внутри транзакции происходит ошибка, и все последующие попытки как-то обработать это событие (записать лог, отправить уведомление) натыкаются на системное сообщение «В данной транзакции уже происходили ошибки». Проанализируем, почему это происходит и как выстроить архитектуру кода правильно.

Почему возникает эта проблема? Выясним причину

Для начала проанализируем ситуацию: в 1С любая ошибка, возникшая между командами НачатьТранзакцию() и ЗафиксироватьТранзакцию(), помечает текущую транзакцию как «битую» или невалидную. Это касается как явных исключений, так и ошибок при записи объектов (например, отказ в процедуре ПередЗаписью, где часто требуется обработчик ПослеЗаписи объекта, или нарушение уникальности).

Важно понимать: в 1С транзакции не являются по-настоящему вложенными. Платформа просто увеличивает счетчик уровня транзакций. Если на «внутреннем» уровне произошла ошибка, это портит всю транзакцию целиком. С этого момента база данных блокирует любые операции записи, чтобы гарантировать целостность данных. Именно поэтому попытка выполнить ЗаписатьВЛог() (если лог пишется в справочник или регистр) внутри блока Исключение вызывает повторную ошибку.

Типичная ошибка в структуре кода

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


Процедура Процедура1()
    НачатьТранзакцию();
    Если Не Функция1() Тогда
        ОтменитьТранзакцию(); // Здесь возникнет ошибка, если транзакция уже "битая"
        ОтправитьПисьмо("Ошибка");
    Иначе
        ЗафиксироватьТранзакцию();
    КонецЕсли;
КонецПроцедуры

Функция Функция1()
    Для Сч = 1 По КоличествоДокументов Цикл
        Попытка
            НовыйДокумент.Записать();
        Исключение
            // Попытка записать в лог (в БД) здесь провалится!
            Сообщить(ОписаниеОшибки()); 
            ЗаписатьВЛог(ОписаниеОшибки()); 
            Возврат Ложь;
        КонецПопытки;
    КонецЦикла;
    Возврат Истина;
КонецФункции

В данном примере, как только НовыйДокумент.Записать() вызывает исключение, транзакция становится невалидной. Любой последующий вызов ЗаписатьВЛог() или попытка программной логики внутри блока Исключение приведет к остановке выполнения. Для корректного оформления конструкций по правилам платформы можно использовать готовые шаблоны текста для применения стандартов и методик разработки 1С.

Решение №1: Перенос блока Попытка-Исключение на верхний уровень

Рассмотрим правильный подход, рекомендованный специалистами и методистами 1С (ИТС). Основная идея заключается в том, что вся транзакция должна быть обернута в один блок Попытка...Исключение. Если произошла любая ошибка, мы должны немедленно выйти из транзакции, откатить её и только потом приступать к логированию.


НачатьТранзакцию();
Попытка
    // Здесь выполняем все действия: запись регистров, создание документов
    ВыполнитьОбработкуДанных();
    СоздатьИЗаписатьДокументы();
    
    ЗафиксироватьТранзакцию();
Исключение
    // ВАЖНО: сначала откатываем транзакцию
    Если ТранзакцияАктивна() Тогда
        ОтменитьТранзакцию();
    КонецЕсли;
    
    // Теперь база данных снова доступна для записи
    ЗаписатьВЛог(ОписаниеОшибки());
    ОтправитьПисьмо("Произошла критическая ошибка выполнения");
КонецПопытки;

Решение №2: Сбор ошибок в массив (аккумуляция контекста)

Часто нам нужно не просто знать, что «произошла ошибка», а понимать, на каком именно документе в цикле всё сломалось. Поскольку записывать в базу внутри «битой» транзакции нельзя, воспользуемся переменными в памяти. Разберем по шагам:

  1. Создаем массив или структуру для хранения описаний ошибок перед началом транзакции.
  2. Внутри вложенных функций при возникновении исключения добавляем информацию об ошибке в этот массив.
  3. Пробрасываем исключение наверх с помощью оператора ВызватьИсключение.
  4. В главном блоке Исключение после команды ОтменитьТранзакцию() обходим массив накопленных ошибок и записываем их в базу данных.

Процедура ОсновнаяОбработка()
    Ошибки = Новый Массив;
    НачатьТранзакцию();
    Попытка
        ОбработатьДокументы(Ошибки);
        ЗафиксироватьТранзакцию();
    Исключение
        Если ТранзакцияАктивна() Тогда
            ОтменитьТранзакцию();
        КонецЕсли;
        
        Для Каждого ТекстОшибки Из Ошибки Цикл
            ЗаписатьВРегистрЛога(ТекстОшибки);
        КонецЦикла;
    КонецПопытки;
КонецПроцедуры

Решение №3: Использование внешних ресурсов для логирования

Если специфика задачи требует записывать лог немедленно (даже если транзакция будет откачена), проанализируем альтернативные способы записи, которые не зависят от состояния текущей транзакции:

Рекомендации по оптимизации транзакций

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

Не подготавливайте данные внутри транзакции. Сначала соберите все необходимые данные в таблицы значений или массивы объектов в памяти, заполните все реквизиты, проверьте условия. И только когда всё готово к записи, открывайте транзакцию, в цикле записывайте подготовленные объекты и сразу закрывайте транзакцию.

Проанализируем пример правильной последовательности:

  1. Чтение данных из внешних источников.
  2. Подготовка коллекции ДокументОбъект (без вызова Записать()).
  3. Проверка заполнения всех объектов.
  4. НачатьТранзакцию().
  5. Цикл записи объектов.
  6. ЗафиксироватьТранзакцию().

Такой подход сокращает время удержания блокировок и уменьшает вероятность того, что вы столкнетесь с ошибкой посередине сложного процесса. Для детального изучения принципов работы с блокируемыми объектами подойдёт материал по оптимистическим и пессимистическим объектным блокировкам в 1С.

Резюме

Если вы получили сообщение «В данной транзакции уже происходили ошибки», это значит, что код пытается обратиться к БД после того, как платформа пометила транзакцию невалидной. Единственный «красивый» способ обработки — поймать ошибку на самом верхнем уровне, выполнить ОтменитьТранзакцию() и только после этого выполнять любые действия по регистрации этой ошибки в базе данных.

← На главную