Как правильно обрабатывать исключения при проведении документов внутри транзакции в 1С?

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

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

Почему транзакция становится «битой»?

Проанализируем ситуацию: вы открываете транзакцию методом НачатьТранзакцию(), затем внутри блока Попытка вызываете проведение документа. Если в процессе проведения (на уровне СУБД или платформы) происходит ошибка, транзакция переходит в состояние Transaction Doomed (обреченная транзакция). Это специфическое состояние, при котором база данных блокирует любые дальнейшие операции в рамках этого соединения до тех пор, пока не будет выполнен полный откат (Rollback).

Рассмотрим основные причины, по которым транзакция мгновенно помечается как неисправимая:

  1. Ошибки СУБД: нарушение уникальности индексов, таймауты блокировок или нехватка места на диске.
  2. Вложенные транзакции: Метод Записать(РежимЗаписиДокумента.Проведение) открывает свою внутреннюю системную транзакцию. Если она завершается аварийно, она «отравляет» вашу внешнюю транзакцию.
  3. Критические ошибки платформы: любые действия, которые нарушают целостность данных в контексте текущего вызова.

Главная ловушка заключается в том, что в блоке Исключение любое обращение к базе данных (даже простое чтение свойства объекта или вызов функции ТранзакцияАктивна()) может спровоцировать новое исключение, так как система видит «битую» транзакцию и не дает совершить ни одного действия, кроме отката.

Разбор правильной структуры кода

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

Посмотрим на пример кода, который считается эталонным:


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

Почему это может не сработать? В новых версиях платформы (начиная с 8.3.15) при возникновении серьезных ошибок на уровне SQL сам вызов функции ТранзакцияАктивна() в блоке исключения может вызвать ошибку «В данной транзакции уже происходили ошибки». Это происходит потому, что функция пытается опросить состояние контекста БД, который уже заблокирован.

Проблема «вакханалии» с переменными и отладчиком

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

Как правильно проводить несколько документов в одной транзакции?

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

  1. Единая точка отката: Не используйте сложные вложенные блоки Попытка...Исключение внутри одной транзакции, если вы не планируете игнорировать ошибки (что в случае с проведением документов крайне опасно).
  2. Минимизация действий в Исключении: В блоке Исключение не делайте запросов к базе. Единственное допустимое действие — отмена транзакции и регистрация ошибки.
  3. Использование описания ошибки: Чтобы не потерять текст исходной ошибки (которая случилась в документе), сохраняйте её сразу в переменную.

Рассмотрим более надежный вариант реализации:


ОписаниеОшибки = "";
НачатьТранзакцию();
Попытка
    // Проводим первый документ
    Документ1.Записать(РежимЗаписиДокумента.Проведение);
    
    // Проводим второй документ
    Документ2.Записать(РежимЗаписиДокумента.Проведение);
    
    ЗафиксироватьТранзакцию();
Исключение
    ОписаниеОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
    
    // Если транзакция была начата, ее НУЖНО отменить. 
    // Если возникнет ошибка на ОтменитьТранзакцию(), она будет обработана внешним уровнем.
    Попытка
        Если ТранзакцияАктивна() Тогда
            ОтменитьТранзакцию();
        КонецЕсли;
    Исключение
        // Здесь мы уже ничего не можем сделать с БД
    КонецПопытки;
    
    ЗаписьЖурналаРегистрации("ПроведениеДокументов", УровеньЖурналаРегистрации.Ошибка, , , ОписаниеОшибки);
    ВызватьИсключение "Ошибка при проведении: " + ОписаниеОшибки;
КонецПопытки;

Рекомендации по стабильной работе

Выясним, какие еще факторы могут влиять на поведение системы:

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

← На главную