Почему запрос в 1С тормозит при поиске по массиву ссылок разных типов?

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

В практике разработки на платформе 1С:Предприятие часто встречается ситуация, когда простой запрос к регистру сведений или накопления начинает работать крайне медленно. Одной из классических причин такой деградации производительности является использование массива документов разных типов в качестве параметра условия ГДЕ ... В (&Массив), если поле имеет составной тип. Разберем подробно, почему это происходит, как платформа взаимодействует с СУБД в этот момент и какие способы оптимизации существуют.

Суть проблемы: почему 0,001 секунды превращаются в 1 секунду?

Рассмотрим типичный запрос. У нас есть регистр сведений, где одно из измерений или ресурсов имеет составной тип (например, ДокументСсылка, включающая в себя все типы документов системы). Мы хотим получить максимальную дату для определенного списка документов:


ВЫБРАТЬ
    Регистр.Документ КАК Документ,
    МАКСИМУМ(Регистр.Дата) КАК Дата
ИЗ
    РегистрСведений.Регистр КАК Регистр
ГДЕ
    Регистр.Документ В (&МассивДокументов)
СГРУППИРОВАТЬ ПО
    Регистр.Документ

Выясним причину разницы в скорости. Когда в &МассивДокументов передаются ссылки только одного типа (например, только «Реализация товаров и услуг»), запрос отрабатывает мгновенно. Но стоит добавить в этот же массив хотя бы один документ другого типа (например, «Поступление товаров и услуг»), время выполнения увеличивается в сотни раз.

Это происходит из-за особенностей хранения составных типов в СУБД и механизмов генерации SQL-запроса платформой 1С. Поле составного типа в таблицах SQL физически представлено не одной колонкой, а несколькими. Обычно это:

  1. _DocumentN_TYPE — бинарный код типа данных.
  2. _DocumentN_RTRef — идентификатор таблицы конкретного типа документа.
  3. _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: Принудительное ограничение типа через ВЫРАЗИТЬ

Если в конкретном алгоритме вы заранее знаете, что в параметре могут быть документы только определенных типов, можно использовать конструкцию ВЫРАЗИТЬ. Проанализируем, как это помогает: ограничивая тип на уровне метаданных запроса, мы подсказываем платформе, к каким именно физическим колонкам и таблицам нужно обращаться.


ГДЕ
    ВЫРАЗИТЬ(Регистр.Документ КАК Документ.РеализацияТоваровУслуг) В (&МассивДокументов)

Однако этот метод неудобен, если типов документов действительно много или они определяются динамически. Он подходит только для жестко заданных бизнес-процессов.

Решение 2: Использование объединения (UNION ALL)

Это один из самых надежных способов ускорить запрос. Вместо того чтобы заставлять СУБД обрабатывать сложный массив в одном условии, мы разделим массив в коде 1С по типам и построим запрос из нескольких частей, объединенных через ОБЪЕДИНИТЬ ВСЕ.

Рассмотрим пример реализации на языке 1С:


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

// 2. Формируем текст запроса динамически
ТекстЗапроса = "";
Счетчик = 0;
Для Каждого КлючИЗначение Из СоответствиеТипов Цикл
    Счетчик = Счетчик + 1;
    ИмяПараметра = "Массив" + Счетчик;
    
    Если Счетчик > 1 Тогда
        ТекстЗапроса = ТекстЗапроса + " ОБЪЕДИНИТЬ ВСЕ ";
    КонецЕсли;
    
    ТекстЗапроса = ТекстЗапроса + 
    "ВЫБРАТЬ Регистр.Документ, МАКСИМУМ(Регистр.Дата)
    |ИЗ РегистрСведений.Регистр КАК Регистр
    |ГДЕ Регистр.Документ В (&" + ИмяПараметра + ")
    |СГРУППИРОВАТЬ ПО Регистр.Документ";
    
    Запрос.УстановитьПараметр(ИмяПараметра, КлючИЗначение.Значение);
КонецЦикла;

При таком подходе для каждого типа документа будет сгенерирован свой простой и эффективный SQL-запрос, который гарантированно попадет в индекс. СУБД выполнит несколько быстрых операций Index Seek и объединит результаты, что в сумме будет в десятки раз быстрее одного медленного сканирования.

Решение 3: Временные таблицы и внутреннее соединение

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

Чтобы гарантировать высокую скорость, мы можем вручную создать временную таблицу, проиндексировать её и использовать ВНУТРЕННЕЕ СОЕДИНЕНИЕ вместо условия В. Посмотрим на этот алгоритм:

  1. Выгрузим массив ссылок в таблицу значений с типизированной колонкой.
  2. Передадим эту таблицу в запрос и поместим во временную таблицу (ВТ).
  3. Создадим индекс по полю ссылки во временной таблице.
  4. Выполним соединение основной таблицы регистра с нашей ВТ.

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

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

Важный нюанс: Начиная с последних версий платформы 8.3.21+, механизмы работы с временными таблицами и составными типами были оптимизированы. Теперь платформа старается строить условия через EXISTS к временным таблицам, что работает значительно лучше старых методов. Тем не менее, для обеспечения стабильности на разных версиях платформы и разных СУБД (MS SQL, PostgreSQL), метод с UNION ALL или предварительной типизацией остается самым надежным.

Подведем итоги

Для ускорения запросов по составным полям придерживайтесь следующих правил:

Соблюдение этих правил позволит вашим запросам работать стабильно быстро, независимо от сложности структуры данных и объема информации в регистре.

← На главную