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

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

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

В чем заключается основная проблема «битой» транзакции?

Проанализируем механизм работы платформы. В 1С:Предприятии используется строгая модель работы с транзакциями СУБД. Если во время выполнения транзакции происходит ошибка уровня базы данных (например, конфликт блокировок, нарушение уникальности или программная установка Отказ = Истина в обработчике проведения), транзакция переходит в состояние невосстановимой ошибки (так называемая «битая» или «отравленная» транзакция).

Рассмотрим ситуацию: мы открыли транзакцию и пытаемся провести 100 документов. На 50-м документе срабатывает проверка остатков, и система устанавливает Отказ = Истина. Даже если мы перехватим это исключение блоком Исключение, транзакция на уровне СУБД уже помечена как подлежащая только откату. Любая последующая попытка записи в базу данных в этой же транзакции (даже запись лога об ошибке) вызовет новое исключение: «В данной транзакции уже происходили ошибки!». Таким образом, один проблемный документ «рушит» всю пачку.

Решение 1: Разделение записи и проведения

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

  1. Открываем транзакцию.
  2. Записываем все документы в режиме записи (без проведения). На этом этапе ошибки возникают редко, так как не работают сложные проверки модулей проведения.
  3. Фиксируем транзакцию.
  4. Вторым циклом, уже вне общей транзакции, пытаемся провести каждый документ по отдельности, оборачивая каждую попытку в свою маленькую транзакцию.

Рассмотрим пример кода для реализации этой стратегии:


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

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

Решение 2: Использование фоновых заданий для изоляции транзакций

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

Проанализируем этот подход по шагам:

  1. В основном цикле мы не проводим документ сами.
  2. Вместо этого мы запускаем фоновое задание (процедуру в общем модуле), которое выполняет Объект.Записать(РежимЗаписиДокумента.Проведение).
  3. Основной поток ожидает завершения задания и анализирует результат.
  4. Если фоновое задание завершилось успешно — документ проведен. Если с ошибкой — это никак не повлияло на транзакцию основного потока.

Этот метод считается довольно «тяжелым» для сервера, поэтому его стоит использовать только в критических случаях, когда нельзя прерывать основную транзакцию.

Решение 3: Правильная реализация мьютекса (блокировки)

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

Вместо использования транзакции как мьютекса, разберем более профессиональный подход с использованием объекта БлокировкаДанных или вспомогательного регистра:

Вариант через объект БлокировкаДанных:


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Справочник.КлючиБлокировок");
ЭлементБлокировки.УстановитьЗначение("Код", "МассовоеПроведениеДокументов");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;

НачатьТранзакцию();
Попытка
    Блокировка.Заблокировать();
    // Теперь код ниже защищен от параллельного запуска
    // Выполняем проведение документов по одному в под-транзакциях
    // ...
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
КонецПопытки;

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

Решение 4: Предварительная проверка (Dry Run)

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

Многие современные конфигурации на базе БСП позволяют выполнить проверку проведения в «холостом» режиме. Если вы заранее знаете, что документ не проведется, вы просто пропускаете его в цикле, не инициируя транзакционную ошибку СУБД. Это позволяет сохранить общую транзакцию «чистой» и работоспособной до самого конца.

Подведем итоги

Для надежной обработки ошибок в транзакциях придерживайтесь следующих правил:

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

← На главную