В практике разработки на платформе 1С:Предприятие часто встречается ситуация, когда простой запрос к регистру сведений или накопления начинает работать крайне медленно. Одной из классических причин такой деградации производительности является использование массива документов разных типов в качестве параметра условия ГДЕ ... В (&Массив), если поле имеет составной тип. Разберем подробно, почему это происходит, как платформа взаимодействует с СУБД в этот момент и какие способы оптимизации существуют.
Рассмотрим типичный запрос. У нас есть регистр сведений, где одно из измерений или ресурсов имеет составной тип (например, ДокументСсылка, включающая в себя все типы документов системы). Мы хотим получить максимальную дату для определенного списка документов:
ВЫБРАТЬ
Регистр.Документ КАК Документ,
МАКСИМУМ(Регистр.Дата) КАК Дата
ИЗ
РегистрСведений.Регистр КАК Регистр
ГДЕ
Регистр.Документ В (&МассивДокументов)
СГРУППИРОВАТЬ ПО
Регистр.Документ
Выясним причину разницы в скорости. Когда в &МассивДокументов передаются ссылки только одного типа (например, только «Реализация товаров и услуг»), запрос отрабатывает мгновенно. Но стоит добавить в этот же массив хотя бы один документ другого типа (например, «Поступление товаров и услуг»), время выполнения увеличивается в сотни раз.
Это происходит из-за особенностей хранения составных типов в СУБД и механизмов генерации SQL-запроса платформой 1С. Поле составного типа в таблицах SQL физически представлено не одной колонкой, а несколькими. Обычно это:
_DocumentN_TYPE — бинарный код типа данных._DocumentN_RTRef — идентификатор таблицы конкретного типа документа._DocumentN_RRRef — уникальный идентификатор (GUID) самой ссылки.Когда вы передаете монотипный массив, оптимизатор 1С понимает, что поиск будет идти только по одной физической таблице. В SQL-запрос уходит простое условие WHERE T1._DocumentN_RRRef IN (СписокGUID). СУБД легко использует индекс по этому полю (выполняется Index Seek).
Когда же вы передаете разнотипный массив, платформа вынуждена строить сложную логику, чтобы проверить соответствие и типа, и ссылки. В SQL это превращается в тяжелую конструкцию с множеством OR или CASE, например:
WHERE ((T1._DocumentTRef = 0x00000186 AND T1._DocumentRRef IN (?))
OR (T1._DocumentTRef = 0x00000187 AND T1._DocumentRRef IN (?)))
Такие условия часто становятся non-SARGable (не пригодными для поиска по индексу). СУБД не может эффективно использовать индекс по полю ссылки, так как оно «зажато» в логические блоки с проверкой типа. В результате вместо быстрого поиска по индексу СУБД переходит к полному сканированию таблицы (Index Scan или Table Scan), что и вызывает колоссальную задержку.
Если в конкретном алгоритме вы заранее знаете, что в параметре могут быть документы только определенных типов, можно использовать конструкцию ВЫРАЗИТЬ. Проанализируем, как это помогает: ограничивая тип на уровне метаданных запроса, мы подсказываем платформе, к каким именно физическим колонкам и таблицам нужно обращаться.
ГДЕ
ВЫРАЗИТЬ(Регистр.Документ КАК Документ.РеализацияТоваровУслуг) В (&МассивДокументов)
Однако этот метод неудобен, если типов документов действительно много или они определяются динамически. Он подходит только для жестко заданных бизнес-процессов.
Это один из самых надежных способов ускорить запрос. Вместо того чтобы заставлять СУБД обрабатывать сложный массив в одном условии, мы разделим массив в коде 1С по типам и построим запрос из нескольких частей, объединенных через ОБЪЕДИНИТЬ ВСЕ.
Рассмотрим пример реализации на языке 1С:
// 1. Распределяем ссылки из исходного массива по типам
СоответствиеТипов = Новый Соответствие;
Для Каждого Ссылка Из МассивДокументов Цикл
ТипСтр = Строка(ТипЗнч(Ссылка));
МассивТипа = СоответствиеТипов.Получить(ТипСтр);
Если МассивТипа = Неопределено Тогда
МассивТипа = Новый Массив;
СоответствиеТипов.Вставить(ТипСтр, МассивТипа);
КонецЕсли;
МассивТипа.Добавить(Ссылка);
КонецЦикла;
// 2. Формируем текст запроса динамически
ТекстЗапроса = "";
Счетчик = 0;
Для Каждого КлючИЗначение Из СоответствиеТипов Цикл
Счетчик = Счетчик + 1;
ИмяПараметра = "Массив" + Счетчик;
Если Счетчик > 1 Тогда
ТекстЗапроса = ТекстЗапроса + " ОБЪЕДИНИТЬ ВСЕ ";
КонецЕсли;
ТекстЗапроса = ТекстЗапроса +
"ВЫБРАТЬ Регистр.Документ, МАКСИМУМ(Регистр.Дата)
|ИЗ РегистрСведений.Регистр КАК Регистр
|ГДЕ Регистр.Документ В (&" + ИмяПараметра + ")
|СГРУППИРОВАТЬ ПО Регистр.Документ";
Запрос.УстановитьПараметр(ИмяПараметра, КлючИЗначение.Значение);
КонецЦикла;
При таком подходе для каждого типа документа будет сгенерирован свой простой и эффективный SQL-запрос, который гарантированно попадет в индекс. СУБД выполнит несколько быстрых операций Index Seek и объединит результаты, что в сумме будет в десятки раз быстрее одного медленного сканирования.
Если массив ссылок очень большой (тысячи элементов), платформа 1С автоматически создает временную таблицу в СУБД для хранения параметров условия В. Однако даже в этом случае оптимизатор СУБД может ошибиться с планом запроса из-за сложности составного поля.
Чтобы гарантировать высокую скорость, мы можем вручную создать временную таблицу, проиндексировать её и использовать ВНУТРЕННЕЕ СОЕДИНЕНИЕ вместо условия В. Посмотрим на этот алгоритм:
// Подготовка ТЗ
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("ДокументСсылка", Новый ОписаниеТипов("ДокументСсылка.РеализацияТоваровУслуг", "ДокументСсылка.ПоступлениеТоваровУслуг")); // Укажите нужные типы
Для Каждого Ссылка Из МассивДокументов Цикл
ТЗ.Добавить().ДокументСсылка = Ссылка;
КонецЦикла;
Запрос.УстановитьПараметр("ТЗДокументов", ТЗ);
Запрос.Текст =
"ВЫБРАТЬ ДокументСсылка ПОМЕСТИТЬ ВТ_Фильтр ИЗ &ТЗДокументов КАК ТЗ ИНДЕКСИРОВАТЬ ПО ДокументСсылка;
|
|ВЫБРАТЬ Регистр.Документ, МАКСИМУМ(Регистр.Дата)
|ИЗ РегистрСведений.Регистр КАК Регистр
|ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Фильтр КАК Фильтр ПО Регистр.Документ = Фильтр.ДокументСсылка
|СГРУППИРОВАТЬ ПО Регистр.Документ";
Важный нюанс: Начиная с последних версий платформы 8.3.21+, механизмы работы с временными таблицами и составными типами были оптимизированы. Теперь платформа старается строить условия через EXISTS к временным таблицам, что работает значительно лучше старых методов. Тем не менее, для обеспечения стабильности на разных версиях платформы и разных СУБД (MS SQL, PostgreSQL), метод с UNION ALL или предварительной типизацией остается самым надежным.
Для ускорения запросов по составным полям придерживайтесь следующих правил:
UNION ALL для каждого типа.ВНУТРЕННИМ СОЕДИНЕНИЕМ и обязательным индексированием.В массивов со ссылками разных типов в высоконагруженных участках кода без предварительной подготовки.Соблюдение этих правил позволит вашим запросам работать стабильно быстро, независимо от сложности структуры данных и объема информации в регистре.