При разработке алгоритмов пакетной обработки данных, перепроведения связанных документов или изменения взаимосвязанных регистров перед разработчиком встает задача обеспечения атомарности: изменения должны либо полностью примениться, либо полностью отмениться при возникновении любой ошибки. Разберем, как соотносятся между собой механизм транзакций и конструкция перехвата исключений Попытка ... Исключение, какие платформенные ограничения существуют в 1С и как писать надежный код.
Для понимания архитектуры работы платформы важно четко разделять назначение этих двух механизмов:
НачатьТранзакцию(), ЗафиксироватьТранзакцию(), ОтменитьТранзакцию()) — управляет целостностью данных на уровне базы данных и СУБД. Она гарантирует, что все операции записи внутри транзакции будут либо зафиксированы вместе, либо откатятся к исходному состоянию. Транзакция не перехватывает программные ошибки.Использовать только Попытка ... Исключение без транзакции для связанных документов нельзя: если первый документ запишется успешно, а на втором возникнет исключение, данные первого документа останутся в базе, что приведет к нарушению логической целостности.
Платформа 1С:Предприятие не поддерживает физических вложенных транзакций в СУБД. При вызове метода Записать() у объекта или набора записей платформа автоматически открывает свою неявную транзакцию. Если до этого в коде уже был вызван метод НачатьТранзакцию(), платформа увеличивает внутренний счетчик уровня транзакций.
Проанализируем ключевое правило: если хотя бы в одной вложенной транзакции произошла ошибка или выполнен откат, вся внешняя транзакция помечается как аварийная (doomed). В платформе 1С действует глобальный признак произошедшей ошибки транзакции. Если после возникновения сбоя внутри незакрытой транзакции программа попытается выполнить запрос, прочитать константу или зафиксировать изменения, возникнет вторичная ошибка: «В данной транзакции уже происходили ошибки».
Эта вторичная ошибка часто маскирует первопричину сбоя, что усложняет диагностику в журнале регистрации и для конечного пользователя.
Рассмотрим эталонный шаблон кода, который исключает возникновение вторичных ошибок и корректно освобождает блокировки данных:
НачатьТранзакцию();
Попытка
// Выполняем запись и проведение взаимосвязанных документов
ДокументОснованиеОбъект.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
СвязанныйДокументОбъект.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
// Фиксация выполняется последней строкой внутри блока Попытка
ЗафиксироватьТранзакцию();
Исключение
// 1. Получаем описание исходной ошибки до любых других действий
ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
// 2. Проверяем активность транзакции и немедленно выполняем откат
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
// 3. Записываем ошибку в системный журнал без участия транзакции БД
ЗаписьЖурналаРегистрации(
"ОбработкаДокументов.ОшибкаПеревешивания",
УровеньЖурналаРегистрации.Ошибка,,,
ТекстОшибки,
РежимЗаписиЖурналаРегистрации.Транзакционная = Ложь
);
// 4. Пробрасываем исключение дальше при необходимости
ВызватьИсключение "Не удалось провести связанные документы: " + ОписаниеОшибки();
КонецПопытки;
Разберем по шагам, почему код должен быть структурирован именно так:
НачатьТранзакцию(): вызов должен располагаться строго перед блоком Попытка. Если поместить открытие транзакции внутрь Попытка, и ошибка возникнет на этапе открытия, блок Исключение попытается отменить транзакцию, которая фактически не была начата.ЗафиксироватьТранзакцию(): фиксация должна быть самой последней операцией внутри блока Попытка. Если любая строчка до нее вызовет исключение, управление сразу перейдет в секцию отката, минуя фиксацию.ТранзакцияАктивна(): в блоке Исключение перед вызовом метода ОтменитьТранзакцию() обязательна проверка активности. Если вложенный обработчик проведения уже сбросил транзакцию, повторный безусловный вызов метода отмены приведет к ошибке выполнения.Исключение: пока открыта транзакция, в которой произошел сбой, нельзя выполнять запросы или записывать объекты. Любая логика сохранения логов в справочники или регистры должна выполняться строго после вызова ОтменитьТранзакцию().ЗаписьЖурналаРегистрации, передавая параметр Транзакционная = Ложь. Это позволяет записать событие непосредственно на диск сервера, минуя откатываемый контекст транзакции СУБД.Для предотвращения взаимных блокировок (deadlocks) при одновременной модификации нескольких объектов рекомендуется соблюдать следующие правила:
НачатьТранзакцию(). Это минимизирует время удержания транзакции и блокировок в СУБД.БлокировкаДанных в режиме управляемых блокировок, накладывая блокировки на документы в строго упорядоченной последовательности (например, по возрастанию ссылок).ФоновыеЗадания.