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

Программист 1С v8.3 (Обычные формы) Управленческий учет Сфера услуг, туризм и социальный сектор
← На главную

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

Как платформа 1С обрабатывает транзакции

Для понимания причин сбоя проанализируем базовый механизм работы платформы с транзакциями. В 1С отсутствует поддержка реальных вложенных транзакций СУБД. Платформа использует внутренний счетчик уровней вложенности:

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

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


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

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

Основные причины самопроизвольного отката транзакций

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

  1. Неявные транзакции и события объектов. Платформа автоматически открывает неявную транзакцию при вызове метода Записать() у справочников, документов или наборов записей регистров. Если в обработчиках ПередЗаписью, ПриЗаписи или в подписках на события взводится флаг Отказ = Истина, то откатываются все изменения, включая те, что были записаны программистом во внешнем коде.
  2. Избыточное использование транзакций в модулях документов. Размещение явных вызовов НачатьТранзакцию() и ЗафиксироватьТранзакцию() внутри процедуры ОбработкаПроведения является грубой ошибкой. Проведение документа платформенно уже выполняется в неделимой транзакции. Любая попытка ручного управления транзакцией внутри проведения ломает логику обработки ошибок платформы.
  3. Интерактивные действия внутри транзакции. Если транзакция открыта до вызова диалоговых окон, открытия модальных форм или опроса внешнего оборудования, транзакция остается активной все время ожидания реакции пользователя. При аварийном закрытии программы, сбое сети или перезагрузке кассового терминала все накопленные за это время данные отменяются целиком.

Методы диагностики и поиска места отмены транзакции

Разберем по шагам, как выявить место в конфигурации, где происходит сбой.

Шаг 1. Анализ журнала регистрации по номеру транзакции.

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

Шаг 2. Проверка состояния транзакции в коде.

Для проверки того, не выполняется ли код внутри чужой незафиксированной транзакции, используем функцию ТранзакцияАктивна(). Добавим проверку перед критическими операциями:


Если ТранзакцияАктивна() Тогда
    // Фиксируем контекст стека вызовов для отладки
    ВызватьИсключение "Обнаружена незафиксированная внешняя транзакция!";
КонецЕсли;

Шаг 3. Изолированное логирование без риска отката.

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


Процедура ЗаписатьСобытиеВЛог(ТекстСообщения, ПутьКФайлу) Экспорт
    ТекстДокумент = Новый ЗаписьТекста(ПутьКФайлу, КодировкаТекста.UTF8, Символы.ВК + Символы.ПС, Истина);
    ТекстДокумент.ЗаписатьСтроку(Строка(ТекущаяДата()) + " | " + ТекстСообщения);
    ТекстДокумент.Закрыть();
КонецПроцедуры

Правила безопасной работы с транзакциями

Чтобы исключить потери данных и ошибки блокировок, придерживаемся следующих правил при доработке конфигураций:

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

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