Как оптимизировать запрос к большой таблице при поиске по составным полям и условиям ЕСТЬNULL

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

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

Проблематика: почему запрос работает медленно?

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


ГДЕ ... И ЕСТЬNULL(ТоварыОтбор.Распоряжение.Ссылка, ТоварыОтбор.Ссылка.Распоряжение) В (&МассивДокументов)

Разберем основные причины низкой производительности этого участка:

  1. Нарушение SARGability (индексируемости): Когда мы оборачиваем поле в функцию (например, ЕСТЬNULL, ВЫБОР или ВЫРАЗИТЬ) непосредственно в секции ГДЕ, СУБД не может использовать индекс по этому полю. Вместо быстрого поиска по индексу (Index Seek) сервер выполняет полное сканирование таблицы (Index Scan), что на 4 миллионах записей крайне затратно.
  2. Разыменование ссылок через точку: Обращение Распоряжение.Ссылка для поля составного типа заставляет платформу 1С генерировать LEFT JOIN со всеми таблицами, входящими в этот составной тип. Если тип включает 10 видов документов, SQL-запрос получит 10 лишних соединений.
  3. Логическое ИЛИ в условиях: Неявное или явное использование ИЛИ в сложных фильтрах часто сбивает оптимизатор SQL с толку, заставляя его выбирать неоптимальный план выполнения.

Решение №1: Разделение запроса через ОБЪЕДИНИТЬ ВСЕ

Рассмотрим наиболее эффективный метод, предложенный в обсуждении. Вместо того чтобы пытаться вычислить «правильное» распоряжение одной строкой, мы разделим задачу на две части и объединим результаты. Это позволит СУБД использовать прямые индексы в каждой части запроса.

Шаг 1. Выбираем записи, где распоряжение заполнено непосредственно в строке ТЧ.

Шаг 2. Выбираем записи, где распоряжение в строке пустое, и берем его из шапки.

Посмотрим на пример реализации такого подхода:


ВЫБРАТЬ
    ТоварыОтбор.Номенклатура,
    ТоварыОтбор.КоличествоОтобрано КАК Количество,
    ТоварыОтбор.Распоряжение КАК Распоряжение
ИЗ
    Документ.ОтборРазмещениеТоваров.ТоварыОтбор КАК ТоварыОтбор
ГДЕ
    ТоварыОтбор.Ссылка.Проведен
    И ТоварыОтбор.Распоряжение В (&МассивДокументов)

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ТоварыОтбор.Номенклатура,
    ТоварыОтбор.КоличествоОтобрано КАК Количество,
    ТоварыОтбор.Ссылка.Распоряжение КАК Распоряжение
ИЗ
    Документ.ОтборРазмещениеТоваров.ТоварыОтбор КАК ТоварыОтбор
ГДЕ
    ТоварыОтбор.Ссылка.Проведен
    И ТоварыОтбор.Распоряжение = ЗНАЧЕНИЕ(Документ.РасходныйОрдерНаТовары.ПустаяСсылка)
    И ТоварыОтбор.Ссылка.Распоряжение В (&МассивДокументов)

В данном случае в первой части запроса работает индекс по полю Распоряжение, а во второй — по полю Ссылка. Это позволяет сократить время выполнения с 5 секунд до долей секунды.

Решение №2: Использование ВТ и индексация параметров

Если массив &МассивДокументов достаточно большой, СУБД может тратить время на его обработку внутри условия В. Проанализируем вариант с использованием временных таблиц. Рассмотрим по шагам:

  1. Поместим входящий массив документов во временную таблицу.
  2. Обязательно проиндексируем поле ссылки во временной таблице.
  3. Выполним соединение с этой таблицей вместо условия В.

// Сначала подготавливаем документы
ВЫБРАТЬ Ссылка В ВТ_Фильтр ИЗ &МассивДокументов КАК Документы ИНДЕКСИРОВАТЬ ПО Ссылка;

// Затем основной запрос
ВЫБРАТЬ ...
ИЗ Документ.ОтборРазмещениеТоваров.ТоварыОтбор КАК ТоварыОтбор
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Фильтр КАК Фильтр ПО ТоварыОтбор.Распоряжение = Фильтр.Ссылка
ГДЕ ...

Важность использования ВЫРАЗИТЬ для составных типов

Как мы выяснили, обращение к реквизитам через точку (Распоряжение.Ссылка) порождает множество соединений. Если мы точно знаем, что в данном контексте нас интересует только определенный тип документа (например, РасходныйОрдерНаТовары), необходимо использовать оператор ВЫРАЗИТЬ. Это подскажет платформе, с какой конкретно таблицей нужно соединяться, исключая лишние LEFT JOIN.

Рассмотрим пример правильного ограничения типа:


ВЫБРАТЬ
    ЕСТЬNULL(ВЫРАЗИТЬ(ТоварыОтбор.Распоряжение КАК Документ.РасходныйОрдерНаТовары).Ссылка, 
             ТоварыОтбор.Ссылка.Распоряжение) КАК Распоряжение
...
ГДЕ
    ТоварыОтбор.Распоряжение ССЫЛКА Документ.РасходныйОрдерНаТовары

Важный нюанс: Даже с ВЫРАЗИТЬ, использование этой конструкции в блоке ГДЕ всё равно может замедлить запрос. Оптимальнее всего выносить такие преобразования в блок ВЫБРАТЬ, а фильтрацию в ГДЕ делать по «чистым» полям.

Проверка индексов в метаданных

Выясним еще одну важную деталь: индексируются ли реквизиты табличных частей? В стандартных конфигурациях 1С реквизиты ТЧ по умолчанию не имеют индекса. Если мы выполняем поиск по полю Распоряжение в таблице из 4 миллионов строк, наличие галочки "Индексировать" в свойствах реквизита в конфигураторе является критически важным.

Проанализируем ситуацию: если индекс отсутствует, никакие ухищрения в тексте запроса не заставят его работать мгновенно, так как SQL-серверу в любом случае придется прочитать все 4 миллиона записей с диска.

Итоговые рекомендации по оптимизации

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

Применяя эти методы, мы превращаем «тяжелый» пятисекундный запрос в эффективный инструмент, который отрабатывает практически незаметно для пользователя.

← На главную