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