В практике разработки на платформе 1С часто возникает задача: существует документ, выполняющий движения в регистрах, и отдельная регламентная процедура или обработка, выполняющая дополнительную бизнес-логику за определенный период. Главная сложность заключается в том, чтобы надежно зафиксировать результат выполнения операции и гарантировать, что один и тот же документ не попадет в выборку повторно.
Разберем основные архитектурные подходы решения этой задачи, выясним их технические ограничения, а также проанализируем влияние каждого метода на параллельность работы пользователей и производительность базы данных.
Рассмотрим самый очевидный на первый взгляд способ: добавить в документ булевский реквизит Обработан и при выполнении алгоритма выставлять его значение в Истина. Если обработка не связана с перепроведением документа, запись объекта выполняется в режиме обычной записи.
Посмотрим на пример кода установки признака:
ОбъектДокумента = СсылкаНаДокумент.ПолучитьОбъект();
ОбъектДокумента.Обработан = Истина;
ОбъектДокумента.Записать(РежимЗаписиДокумента.Запись);
Несмотря на простоту реализации, этот вариант несет в себе серьезные архитектурные риски:
Записать() платформа перезаписывает реквизит ВерсияДанных. Если в этот момент пользователь редактирует данный документ в интерактивной форме, при попытке сохранения он получит ошибку несоответствия версии объекта.ПередЗаписью, ПриЗаписи и все зарегистрированные подписки на события. Это может вызвать ненужные проверки заполнения, пересчеты и замедлить фоновый процесс.Таким образом, изменение самого объекта оправдано только в том случае, если логика обработки напрямую совмещена с проведением документа и не выполняется в асинхронном режиме отдельно от пользователей.
Проанализируем более гибкий и безопасный подход: создание непериодического независимого регистра сведений, например, ОбработанныеДокументы. В качестве измерения выступает Документ с типом ссылки на обрабатываемый объект метаданных.
При настройке регистра важно включить свойство Ведущее для измерения Документ. Это гарантирует, что при физическом удалении документа из базы платформа автоматически очистит связанную запись в регистре сведений, исключая появление «битых» ссылок.
При выборке документов для обработки мы исключаем те, которые уже присутствуют в регистре. Разберем правильный запрос с точки зрения оптимизации СУБД:
ВЫБРАТЬ
Док.Ссылка КАК Документ
ИЗ
Документ.РеализацияТоваровУслуг КАК Док
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ОбработанныеДокументы КАК Обработанные
ПО Док.Ссылка = Обработанные.Документ
ГДЕ
Док.Проведен = ИСТИНА
И Док.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
И Обработанные.Документ ЕСТЬ NULL
Обратите внимание на соединение: использование конструкции ЛЕВОЕ СОЕДИНЕНИЕ с проверкой ЕСТЬ NULL работает значительно быстрее на больших объемах данных, чем подзапрос с условием ГДЕ Док.Ссылка НЕ В (ВЫБРАТЬ ...).
После успешного выполнения бизнес-логики мы фиксируем документ в регистре сведений с помощью набора записей:
НаборЗаписей = РегистрыСведений.ОбработанныеДокументы.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Документ.Установить(СсылкаНаДокумент);
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Документ = СсылкаНаДокумент;
НоваяЗапись.ПериодОбработки = ТекущаяДатаСеанса();
НаборЗаписей.Записать();
Этот вариант не блокирует объект документа, не вызывает системных подписок и полностью изолирует вспомогательный алгоритм от пользовательской активности.
В высоконагруженных системах (например, в типовых решениях уровня 1С:ERP) хранить постоянный список всех когда-либо обработанных документов может быть неэффективно из-за непрерывного роста таблицы. Вместо этого используется модель очереди.
Рассмотрим схему работы очереди:
ОчередьОбработкиДокументов с измерением Документ и реквизитами Статус (Перечисление: «К обработке», «В обработке»), а также ДатаНачалаОбработки.ОбработкаПроведения нужного документа добавляем запись в регистр со статусом К обработке. Документ попадает в очередь автоматически при проведении.Выясним причину возможных сбоев в такой схеме: если фоновое задание аварийно завершится (например, по таймауту или перезапуску службы сервера), записи могут навсегда остаться со статусом В обработке. Чтобы этого избежать, перед началом выборки необходимо выполнять сброс зависших задач:
// Сбрасываем задачи, зависшие более чем на 30 минут
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Очередь.Документ КАК Документ
|ИЗ
| РегистрСведений.ОчередьОбработкиДокументов КАК Очередь
|ГДЕ
| Очередь.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыОчереди.ВОбработке)
| И Очередь.ДатаНачалаОбработки < &ГраничноеВремя";
Запрос.УстановитьПараметр("ГраничноеВремя", ТекущаяДатаСеанса() - 1800);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Возвращаем статус обратно в "К обработке"
КонецЦикла;
Если конфигурация нестандартная, но не хочется создавать лишние структуры регистров и писать логику их очистки, рассмотрим встроенные платформенные инструменты:
ПланыОбмена.УдалитьРегистрациюИзменений(). Платформа берет на себя всю работу по поддержанию системных таблиц изменений.Выбирая способ отметки обработанных документов, ориентируйтесь на архитектурные требования задачи: