Как оптимизировать запрос к остаткам и ускорить массовое перепроведение документов по ФИФО в 1С?

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

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

Проанализируем исходный запрос к регистру остатков, выполняемый для каждого документа:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	ВзаиморасчетыОстатки.Документ,
|	ВзаиморасчетыОстатки.СуммаОстаток,
|	ВзаиморасчетыОстатки.Партнер,
|	ВзаиморасчетыОстатки.Документ.Дата КАК ДокументДата
|ИЗ
|	РегистрНакопления.Взаиморасчеты.Остатки(
|			&Дата,
|			Партнер = &Партнер
|				И Документ <> &Пуст) КАК ВзаиморасчетыОстатки
|
|УПОРЯДОЧИТЬ ПО
|	ДокументДата";

Запрос.УстановитьПараметр("Дата", Док.Ссылка.МоментВремени());
Запрос.УстановитьПараметр("Пуст", Неопределено);
Запрос.УстановитьПараметр("Партнер", Док.Партнер);

1. Устранение неявных клиент-серверных вызовов в цикле

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


// Ошибочный подход — вызов на каждой итерации
Состояние("Проводим " + Стр.Док, (тек * 100) / всего, "Номер док " + тек);

При 85 000 документов такой вызов отнимает до 90% всего времени работы процедуры. Если требуется визуализация прогресса, обновлять состояние следует не на каждом документе, а с определенным шагом (например, раз в 500–1000 итераций) или запускать тяжелую обработку через ФоновыеЗадания на стороне сервера без взаимодействия с клиентом.

2. Разыменование составных типов через точку (LEFT JOIN ко всем таблицам)

Обратим внимание на конструкцию ВзаиморасчетыОстатки.Документ.Дата. Если измерение Документ имеет составной тип данных, платформа 1С преобразует обращение через точку в неявные левые соединения (LEFT OUTER JOIN) со всеми таблицами типов, входящих в составной тип. Это многократно утяжеляет план выполнения запроса в СУБД.

Для устранения этой проблемы применим следующие решения:

  1. Хранение даты в регистре: добавим в регистр накопления отдельный реквизит или измерение ДатаДокумента с типом Дата и включенной индексацией. Это позволит сортировать результат напрямую по полю регистра без разыменований.
  2. Использование конструкции ВЫРАЗИТЬ: если составной тип неизбежен, но в конкретной ситуации тип документа известен, явно ограничим его: ВЫРАЗИТЬ(ВзаиморасчетыОстатки.Документ КАК Документ.РеализацияТоваровУслуг).Дата.

3. Оптимизация структуры измерений и условий в виртуальных таблицах

Разберем порядок измерений регистра накопления и условия фильтрации:

  1. Порядок измерений: индекс таблицы итогов формируется СУБД строго в порядке следования измерений в метаданных. Если первым измерением идет Документ, а вторым Партнер, то фильтрация только по Партнер не может эффективно использовать индекс таблицы итогов. Для расчетов взаиморасчетов первым измерением должен выступать Партнер (или Договор), а вторым — расчетный Документ.
  2. Отказ от оператора неравенства: условие Документ <> &Пуст лишает СУБД возможности выполнить точечный поиск по индексу (Index Seek), приводя к сканированию диапазона или всей таблицы. Для авансов и постоплат рекомендуется разделять учет по отдельным измерениям или флагам, чтобы фильтрация выполнялась только по строгому равенству (=).
  3. Передача параметров без разыменований: обращение к реквизитам через точку в параметрах (Док.Ссылка.МоментВремени(), Док.Партнер) вызывает дополнительные обращения к базе. Значения реквизитов следует получать заранее из выборки или структуры.

4. Пакетная выборка и расчет ФИФО без запросов в цикле

Выполнение отдельного запроса к виртуальной таблице остатков на каждый документ многократно увеличивает накладные расходы. Рассмотрим алгоритм перевода на пакетную обработку:

  1. Формирование порции данных: загружаем список документов и партнеров во временную таблицу через МенеджерВременныхТаблиц.
  2. Срез остатков одним запросом: получаем остатки сразу по всем партнерам пакета на требуемую границу времени. Для точного позиционирования используем объект Граница: ГраницаОстатков = Новый Граница(ТекущийМоментВремени, ВидГраницы.Исключая);
  3. Распределение в памяти: сопоставляем документы расчетов и авансы с помощью индексированной таблицы значений (ТаблицаЗначений.Колонки.Индексы.Добавить()) или вычисляем распределение списания накопительным итогом прямо в теле запроса.

5. Перезапись движений вместо полного перепроведения

Если логика распределения меняет движения только по одному конкретному регистру, не следует вызывать полное перепроведение документа через ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение). Полное проведение инициирует проверку всех модулей, блокировку всех связанных объектов и перезапись всех регистров документа.

Оптимальный подход — формировать и записывать только нужный набор записей регистра:


НаборЗаписей = РегистрыНакопления.Взаиморасчеты.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);

// Заполняем движения набора согласно рассчитанному распределению ФИФО
Для Каждого Стр Из ТаблицаРаспределения Цикл
	Движение = НаборЗаписей.Добавить();
	Движение.Период = ДатаДокумента;
	Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
	Движение.Партнер = Партнер;
	Движение.Документ = Стр.ДокументОснование;
	Движение.Сумма = Стр.СуммаСписания;
КонецЦикла;

// Записываем только движения без вызова обработчиков проведения документа
НаборЗаписей.Записать();

6. Управление транзакциями и блокировками данных

Для стабильной работы алгоритма на больших объемах данных соблюдаем правила транзакционности:

  1. Пакетная фиксация транзакций: оборачиваем обработку порций (по 500–1000 документов или по отдельному партнеру) в явные блоки НачатьТранзакцию() / ЗафиксироватьТранзакцию(), чтобы не переполнять журнал транзакций СУБД.
  2. Управляемые блокировки: перед расчетом остатков и записью движений накладываем явную блокировку через БлокировкаДанных по измерению Партнер для предотвращения взаимных блокировок (дедлоков) при параллельной работе пользователей.
← На главную