Почему запись в независимом регистре сведений удаляется при отказе проведения документа?

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

В практике разработки на платформе 1С:Предприятие 8 часто возникает ситуация, когда при программной доработке логики проведения документа программисты сталкиваются с «мистическим» исчезновением данных. Типичный пример: мы записываем данные в независимый регистр сведений (например, фиксируем номер счета-фактуры) в процедуре ПередЗаписью или ОбработкаПроведения. Если проведение документа завершается ошибкой (отказом), запись в регистре исчезает, хотя регистр по определению является «независимым» и не подчиняется регистратору. Разберем подробно, почему это происходит и как правильно организовать работу с данными в таких случаях.

Механизм неявных транзакций в 1С

Главная причина, по которой запись в независимом регистре удаляется, кроется в устройстве транзакционной модели 1С. Проанализируем жизненный цикл записи документа. Когда пользователь нажимает кнопку «Ок» (Записать и провести), платформа инициирует неявную транзакцию базы данных. Эта транзакция охватывает все события, происходящие на сервере с момента начала записи до её завершения:

Важно понимать: хотя регистр называется «независимым», любая программная запись в него (через МенеджерЗаписи или НаборЗаписей), выполненная внутри этого интервала, становится частью общей транзакции. Если в любой момент (например, при проверке остатков в ОбработкаПроведения) система встречает команду Отказ = Истина или происходит исключение, платформа выполняет команду ROLLBACK (откат транзакции) для СУБД. В этот момент абсолютно все изменения, внесенные в базу данных в рамках этой транзакции, аннулируются. Это касается и изменений в реквизитах самого документа, и записей в любых регистрах (включая независимые), и даже создания других документов программным способом.

Различие между Объектом и Базой Данных

Рассмотрим ситуацию, которая часто сбивает с толку начинающих разработчиков: почему после отказа проведения номер в поле документа на форме остается измененным, а в базе данных его нет? Выясним причину этого расхождения.

В 1С существует два состояния данных: состояние в оперативной памяти (объект ДокументОбъект) и состояние в таблицах СУБД. Когда мы пишем в коде ЭтотОбъект.Номер = "НовыйНомер-001", мы меняем свойство объекта в памяти сервера. При выполнении транзакции это значение записывается в БД. Однако при отказе (ROLLBACK) база данных возвращается к исходному состоянию, а объект в памяти сервера — нет. После завершения серверного вызова этот измененный объект возвращается обратно на форму. Пользователь видит «новый» номер, но если он закроет документ без сохранения и откроет его снова, он увидит старое значение (или пустое поле), так как в базе данных транзакция не была зафиксирована (COMMIT).

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

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

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

Как правильно организовать запись в регистр

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

1. Использование события ПослеЗаписи на клиенте

Если действие инициируется интерактивно, мы можем использовать обработчик ПослеЗаписи в модуле формы. К этому моменту транзакция записи документа уже успешно завершена и зафиксирована в БД. Мы можем выполнить отдельный серверный вызов для записи данных в регистр. Но помните: этот метод не сработает, если документ записывается программно без участия формы.

2. Механизм фоновых заданий

Это наиболее надежный способ сделать запись «независимой» от основной транзакции. Из модуля документа мы можем запустить фоновое задание, которое выполнит запись в регистр в отдельном сеансе. Поскольку у фонового задания будет своя собственная транзакция, откат основной транзакции документа на него не повлияет.

Посмотрим на пример кода для запуска записи через фоновое задание:


// В модуле документа
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
        ПараметрыЗаписи = Новый Структура("Номер, Дата", ЭтотОбъект.Номер, ЭтотОбъект.Дата);
        ФоновыеЗадания.Выполнить("МойОбщийМодуль.ЗаписатьДанныеВРегистр", ПараметрыЗаписи);
    КонецЕсли;
КонецПроцедуры

3. Проверка существования записи

Как справедливо отмечено в обсуждении, логика «если номер присвоен, значит, он есть в регистре» — ошибочна. Необходимо всегда проверять наличие записи физически в базе данных перед выполнением действий. Рассмотрим правильный алгоритм обновления записи в независимом регистре:


МенеджерЗаписи = РегистрыСведений.МойСпецРегистр.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Период = ЭтотОбъект.Дата;
// Установка измерений
МенеджерЗаписи.ИдентификаторДокумента = ЭтотОбъект.Ссылка; 

МенеджерЗаписи.Прочитать();

Если Не МенеджерЗаписи.Выбран() Или МенеджерЗаписи.Номер <> ЭтотОбъект.Номер Тогда
    МенеджерЗаписи.Номер = ЭтотОбъект.Номер;
    // Заполняем остальные ресурсы
    Попытка
        МенеджерЗаписи.Записать();
    Исключение
        ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Ошибка записи в регистр: " + ОписаниеОшибки());
    КонецПопытки;
КонецЕсли;

Рекомендации по архитектуре системы

Подводя итог, проанализируем лучшие практики для реализации подобного функционала:

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

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

← На главную