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