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

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

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

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

Причины возникновения ошибки на уровне платформы

Рассмотрим, что происходит внутри платформы 1С при работе с транзакциями СУБД. Главная причина появления ошибки заключается в том, как платформа обрабатывает вложенность и сбои внутри транзакционных блоков.

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

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


// Ошибочный подход: попытка обработать ошибку базы данных внутри транзакции
НачатьТранзакцию();
Попытка
    // Какая-либо операция с базой данных
    ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
    ЗафиксироватьТранзакцию();
Исключение
    // Даже если ошибка перехвачена, транзакция СУБД уже испорчена
    ОтменитьТранзакцию();
    // Любой следующий запрос к БД в этом же контексте вызовет ошибку!
КонецПопытки;

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

Альтернативные способы решения задачи

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

Способ 1. Вынос расчета в модуль менеджера или общий модуль

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

Разберем пример реализации такой функции:


// Модуль менеджера типового документа
Функция РассчитатьТаблицуДвижений(ПараметрыРасчета) Экспорт

    ТаблицаДвижений = Новый ТаблицаЗначений;
    // Добавление колонок и выполнение расчетов на основании входящих параметров...
    
    Возврат ТаблицаДвижений;

КонецФункции

В этом случае в нетиповом документе достаточно передать параметры в эту функцию и получить готовую таблицу с движениями прямо в оперативной памяти.

Способ 2. Выполнение логики в отдельном фоновом задании

Если изменить код типового документа невозможно или затратно, можно вынести создание, проведение и отмену временного документа в фоновое задание. Фоновое задание выполняется в отдельном сеансе со своей независимой транзакцией. Откат транзакции в фоновом задании никак не повлияет на транзакцию основного документа.

Посмотрим на пример вызова фонового задания:


// Вызов фонового задания для получения рассчитанных данных
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(СтруктураПараметров);

Задание = ФоновыеЗадания.Выполнить("МойОбщийМодуль.СформироватьДвиженияВоВнешнемСеансе", ПараметрыЗадания);
Задание.ОжидатьЗавершения();

РезультатРасчета = Задание.ПолучитьЗначение();

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

Способ 3. Предварительное создание и физическое удаление объекта вне транзакции

Если расчет необходимо сделать именно физическим проведением документа, эту операцию следует выполнять до открытия транзакции проведения основного документа (например, перед вызовом метода Записать() на клиенте или в обработчике кнопки).

  1. Создаем временный типовой документ и записываем его вне транзакции проведения.
  2. Проводим временный документ методом Записать(РежимЗаписиДокумента.Проведение).
  3. Считываем движения из регистров базы данных.
  4. Непосредственно удаляем временный документ методом Удалить() или помечаем его на удаление.
  5. Передаем полученные движения в основной документ и проводим его.

Таким образом, мы избегаем использования ОтменитьТранзакцию() во вложенном контексте и сохраняем стабильность работы СУБД.

← На главную