Как оптимизировать запрос к регистру СоответствиеНоменклатурыВЕТИС и ускорить сопоставление продукции?

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

При работе с интеграцией ВЕТИС (система «Меркурий») в типовых конфигурациях, таких как 1С:Бухгалтерия 3.0, разработчики часто сталкиваются с критическим падением производительности. Проблема обычно локализуется в общем модуле ИнтеграцияВЕТИСБП, а именно в процедуре ЗаполнитьТоварыВЕТИС. Когда количество записей в регистре сведений СоответствиеНоменклатурыВЕТИС достигает десятков тысяч, а в документе присутствует несколько десятков строк, время выполнения простого сопоставления может вырасти до 5–10 минут. Рассмотрим, почему это происходит и как исправить ситуацию несколькими способами.

Анализ причины низкой производительности

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

На уровне СУБД (особенно MS SQL Server) такая конструкция при большом объеме данных создает «декартово произведение» промежуточных результатов. Группировка СГРУППИРОВАТЬ ПО и агрегатные функции МАКСИМУМ вместе с КОЛИЧЕСТВО(РАЗЛИЧНЫЕ) вынуждены обрабатывать огромный массив данных в памяти или в tempdb, что и приводит к задержкам. Кроме того, использование ЕСТЬNULL внутри функций агрегации мешает оптимизатору эффективно использовать индексы.

Способ 1: Оптимизация условий соединения (самый быстрый результат)

Самый простой и эффективный способ, который на практике показывает ускорение в сотни раз (с 146 секунд до 0.05 секунды), — это избавление от второго левого соединения. Мы можем объединить условия поиска в одно соединение, используя логический оператор ИЛИ для серии.

Рассмотрим, как изменить запрос:


|ВЫБРАТЬ
|    МАКСИМУМ(СоответствиеНоменклатурыВЕТИС.Продукция) КАК Продукция,
|    МАКСИМУМ(СоответствиеНоменклатурыВЕТИС.ЗаписьСкладскогоЖурнала) КАК ЗаписьСкладскогоЖурнала,
|    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ СоответствиеНоменклатурыВЕТИС.Продукция) КАК ПродукцияКоличествоРазличные,
|    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ СоответствиеНоменклатурыВЕТИС.ЗаписьСкладскогоЖурнала) КАК ЗаписьСкладскогоЖурналаКоличествоРазличные,
|    ТабличнаяЧасть.Номенклатура КАК Номенклатура,
|    ТабличнаяЧасть.Характеристика КАК Характеристика,
|    ТабличнаяЧасть.Серия КАК Серия
|ПОМЕСТИТЬ СопоставленыеПозиции
|ИЗ
|    ТоварыКОформлению КАК ТабличнаяЧасть
|        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СоответствиеНоменклатурыВЕТИС КАК СоответствиеНоменклатурыВЕТИС
|        ПО (СоответствиеНоменклатурыВЕТИС.Номенклатура = ТабличнаяЧасть.Номенклатура)
|            И (СоответствиеНоменклатурыВЕТИС.Характеристика = ТабличнаяЧасть.Характеристика)
|            И (СоответствиеНоменклатурыВЕТИС.Серия = ТабличнаяЧасть.Серия 
|               ИЛИ СоответствиеНоменклатурыВЕТИС.Серия = &ПустаяСерия)
|
|СГРУППИРОВАТЬ ПО
|    ТабличнаяЧасть.Номенклатура,
|    ТабличнаяЧасть.Характеристика,
|    ТабличнаяЧасть.Серия

Почему это работает: Мы сократили количество обрабатываемых таблиц. SQL-серверу гораздо проще выполнить один Index Seek по измерениям регистра, чем пытаться сопоставить две выборки из одной и той же таблицы. Обратите внимание, что мы убрали ЕСТЬNULL из полей выбора, так как теперь данные приходят из одного источника.

Способ 2: Использование временных таблиц с предварительным отбором

Если данных в регистре слишком много (сотни тысяч или миллионы), даже условие ИЛИ может начать замедляться. В этом случае правильным архитектурным подходом в 1С будет «разрезание» запроса с использованием временных таблиц. Это гарантирует стабильную работу независимо от планов запроса СУБД.

Разберем этот метод по шагам:

  1. Выделим все уникальные товары из документа во временную таблицу ВТ_НоменклатураДокумента.
  2. Проиндексируем эту таблицу по полям Номенклатура, Характеристика.
  3. Выберем данные из регистра СоответствиеНоменклатурыВЕТИС только для тех товаров, которые есть в нашей временной таблице, и поместим их в ВТ_ДанныеРегистра.
  4. Соединим две небольшие временные таблицы.

Пример реализации второго шага (отбор данных регистра):


ВЫБРАТЬ РАЗЛИЧНЫЕ
    Номенклатура,
    Характеристика
ПОМЕСТИТЬ ВТ_Фильтр
ИЗ
    ТоварыКОформлению;

ИНДЕКСИРОВАТЬ ПО
    Номенклатура,
    Характеристика;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    РС.Номенклатура,
    РС.Характеристика,
    РС.Серия,
    РС.Продукция,
    РС.ЗаписьСкладскогоЖурнала
ПОМЕСТИТЬ ВТ_ДанныеРегистра
ИЗ
    РегистрСведений.СоответствиеНоменклатурыВЕТИС КАК РС
ГДЕ
    (РС.Номенклатура, РС.Характеристика) В (ВЫБРАТЬ Номенклатура, Характеристика ИЗ ВТ_Фильтр);

Такой подход заставляет систему работать только с релевантными записями, исключая полное сканирование (Full Scan) огромного регистра.

Способ 3: Использование ОБЪЕДИНИТЬ ВСЕ вместо логических условий

Проанализируем еще одну ситуацию: иногда оптимизатор SQL некорректно обрабатывает условие ИЛИ в связках (Joins). Чтобы принудительно заставить систему использовать индексы максимально эффективно, можно применить объединение двух запросов.

В первой части объединения мы выбираем только точные соответствия по сериям. Во второй части — только соответствия по «пустым» сериям. Затем результат схлопывается группировкой. Это исключает сложные логические развилки внутри самого механизма соединения таблиц.

Рекомендации по обслуживанию базы данных

Помимо правки кода запроса, важно помнить о регламентных операциях на уровне СУБД. Если вы используете MS SQL Server 2012 или более новые версии, обратите внимание на следующие моменты:

Подводя итог, можно сказать, что наиболее сбалансированным решением для типовой БП 3.0 является замена двойного соединения на одно с условием ИЛИ. Это требует минимальных правок в коде и дает взрывной рост производительности за счет исключения избыточного сканирования данных.

← На главную