При работе с интеграцией ВЕТИС (система «Меркурий») в типовых конфигурациях, таких как 1С:Бухгалтерия 3.0, разработчики часто сталкиваются с критическим падением производительности. Проблема обычно локализуется в общем модуле ИнтеграцияВЕТИСБП, а именно в процедуре ЗаполнитьТоварыВЕТИС. Когда количество записей в регистре сведений СоответствиеНоменклатурыВЕТИС достигает десятков тысяч, а в документе присутствует несколько десятков строк, время выполнения простого сопоставления может вырасти до 5–10 минут. Рассмотрим, почему это происходит и как исправить ситуацию несколькими способами.
Проанализируем исходный код проблемного участка. В типовом решении часто используется двойное левое соединение к одной и той же таблице регистра. Первое соединение ищет соответствие с учетом конкретной серии номенклатуры, а второе — по пустой серии (универсальное соответствие для товара).
На уровне СУБД (особенно MS SQL Server) такая конструкция при большом объеме данных создает «декартово произведение» промежуточных результатов. Группировка СГРУППИРОВАТЬ ПО и агрегатные функции МАКСИМУМ вместе с КОЛИЧЕСТВО(РАЗЛИЧНЫЕ) вынуждены обрабатывать огромный массив данных в памяти или в tempdb, что и приводит к задержкам. Кроме того, использование ЕСТЬNULL внутри функций агрегации мешает оптимизатору эффективно использовать индексы.
Самый простой и эффективный способ, который на практике показывает ускорение в сотни раз (с 146 секунд до 0.05 секунды), — это избавление от второго левого соединения. Мы можем объединить условия поиска в одно соединение, используя логический оператор ИЛИ для серии.
Рассмотрим, как изменить запрос:
|ВЫБРАТЬ
| МАКСИМУМ(СоответствиеНоменклатурыВЕТИС.Продукция) КАК Продукция,
| МАКСИМУМ(СоответствиеНоменклатурыВЕТИС.ЗаписьСкладскогоЖурнала) КАК ЗаписьСкладскогоЖурнала,
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ СоответствиеНоменклатурыВЕТИС.Продукция) КАК ПродукцияКоличествоРазличные,
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ СоответствиеНоменклатурыВЕТИС.ЗаписьСкладскогоЖурнала) КАК ЗаписьСкладскогоЖурналаКоличествоРазличные,
| ТабличнаяЧасть.Номенклатура КАК Номенклатура,
| ТабличнаяЧасть.Характеристика КАК Характеристика,
| ТабличнаяЧасть.Серия КАК Серия
|ПОМЕСТИТЬ СопоставленыеПозиции
|ИЗ
| ТоварыКОформлению КАК ТабличнаяЧасть
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СоответствиеНоменклатурыВЕТИС КАК СоответствиеНоменклатурыВЕТИС
| ПО (СоответствиеНоменклатурыВЕТИС.Номенклатура = ТабличнаяЧасть.Номенклатура)
| И (СоответствиеНоменклатурыВЕТИС.Характеристика = ТабличнаяЧасть.Характеристика)
| И (СоответствиеНоменклатурыВЕТИС.Серия = ТабличнаяЧасть.Серия
| ИЛИ СоответствиеНоменклатурыВЕТИС.Серия = &ПустаяСерия)
|
|СГРУППИРОВАТЬ ПО
| ТабличнаяЧасть.Номенклатура,
| ТабличнаяЧасть.Характеристика,
| ТабличнаяЧасть.Серия
Почему это работает: Мы сократили количество обрабатываемых таблиц. SQL-серверу гораздо проще выполнить один Index Seek по измерениям регистра, чем пытаться сопоставить две выборки из одной и той же таблицы. Обратите внимание, что мы убрали ЕСТЬNULL из полей выбора, так как теперь данные приходят из одного источника.
Если данных в регистре слишком много (сотни тысяч или миллионы), даже условие ИЛИ может начать замедляться. В этом случае правильным архитектурным подходом в 1С будет «разрезание» запроса с использованием временных таблиц. Это гарантирует стабильную работу независимо от планов запроса СУБД.
Разберем этот метод по шагам:
ВТ_НоменклатураДокумента.Номенклатура, Характеристика.СоответствиеНоменклатурыВЕТИС только для тех товаров, которые есть в нашей временной таблице, и поместим их в ВТ_ДанныеРегистра.Пример реализации второго шага (отбор данных регистра):
ВЫБРАТЬ РАЗЛИЧНЫЕ
Номенклатура,
Характеристика
ПОМЕСТИТЬ ВТ_Фильтр
ИЗ
ТоварыКОформлению;
ИНДЕКСИРОВАТЬ ПО
Номенклатура,
Характеристика;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
РС.Номенклатура,
РС.Характеристика,
РС.Серия,
РС.Продукция,
РС.ЗаписьСкладскогоЖурнала
ПОМЕСТИТЬ ВТ_ДанныеРегистра
ИЗ
РегистрСведений.СоответствиеНоменклатурыВЕТИС КАК РС
ГДЕ
(РС.Номенклатура, РС.Характеристика) В (ВЫБРАТЬ Номенклатура, Характеристика ИЗ ВТ_Фильтр);
Такой подход заставляет систему работать только с релевантными записями, исключая полное сканирование (Full Scan) огромного регистра.
Проанализируем еще одну ситуацию: иногда оптимизатор SQL некорректно обрабатывает условие ИЛИ в связках (Joins). Чтобы принудительно заставить систему использовать индексы максимально эффективно, можно применить объединение двух запросов.
В первой части объединения мы выбираем только точные соответствия по сериям. Во второй части — только соответствия по «пустым» сериям. Затем результат схлопывается группировкой. Это исключает сложные логические развилки внутри самого механизма соединения таблиц.
Помимо правки кода запроса, важно помнить о регламентных операциях на уровне СУБД. Если вы используете MS SQL Server 2012 или более новые версии, обратите внимание на следующие моменты:
UPDATE STATISTICS для таблицы регистра.СоответствиеНоменклатурыВЕТИС крайне важно, чтобы измерения шли в порядке Номенклатура -> Характеристика -> Серия. Если порядок изменен, стандартные индексы могут перестать работать для частичных отборов.Hash Match на соединении таких таблиц, это верный признак того, что индексы не используются и нужно переходить к варианту с временными таблицами.Подводя итог, можно сказать, что наиболее сбалансированным решением для типовой БП 3.0 является замена двойного соединения на одно с условием ИЛИ. Это требует минимальных правок в коде и дает взрывной рост производительности за счет исключения избыточного сканирования данных.