При работе с высоконагруженными базами данных 1С, где таблицы содержат миллионы записей, даже визуально простой запрос может стать причиной серьезных задержек. Рассмотрим ситуацию: необходимо выбрать данные из табличной части документа ОтборРазмещениеТоваров, объем которой составляет около 4 миллионов строк. Основная сложность заключается в том, что поле распоряжения может быть заполнено как в строке табличной части, так и в шапке документа.
Проанализируем исходное условие, которое вызывало задержку в 5 секунд:
ГДЕ ... И ЕСТЬNULL(ТоварыОтбор.Распоряжение.Ссылка, ТоварыОтбор.Ссылка.Распоряжение) В (&МассивДокументов)
Разберем основные причины низкой производительности этого участка:
ЕСТЬNULL, ВЫБОР или ВЫРАЗИТЬ) непосредственно в секции ГДЕ, СУБД не может использовать индекс по этому полю. Вместо быстрого поиска по индексу (Index Seek) сервер выполняет полное сканирование таблицы (Index Scan), что на 4 миллионах записей крайне затратно.Распоряжение.Ссылка для поля составного типа заставляет платформу 1С генерировать LEFT JOIN со всеми таблицами, входящими в этот составной тип. Если тип включает 10 видов документов, SQL-запрос получит 10 лишних соединений.ИЛИ в сложных фильтрах часто сбивает оптимизатор SQL с толку, заставляя его выбирать неоптимальный план выполнения.Рассмотрим наиболее эффективный метод, предложенный в обсуждении. Вместо того чтобы пытаться вычислить «правильное» распоряжение одной строкой, мы разделим задачу на две части и объединим результаты. Это позволит СУБД использовать прямые индексы в каждой части запроса.
Шаг 1. Выбираем записи, где распоряжение заполнено непосредственно в строке ТЧ.
Шаг 2. Выбираем записи, где распоряжение в строке пустое, и берем его из шапки.
Посмотрим на пример реализации такого подхода:
ВЫБРАТЬ
ТоварыОтбор.Номенклатура,
ТоварыОтбор.КоличествоОтобрано КАК Количество,
ТоварыОтбор.Распоряжение КАК Распоряжение
ИЗ
Документ.ОтборРазмещениеТоваров.ТоварыОтбор КАК ТоварыОтбор
ГДЕ
ТоварыОтбор.Ссылка.Проведен
И ТоварыОтбор.Распоряжение В (&МассивДокументов)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ТоварыОтбор.Номенклатура,
ТоварыОтбор.КоличествоОтобрано КАК Количество,
ТоварыОтбор.Ссылка.Распоряжение КАК Распоряжение
ИЗ
Документ.ОтборРазмещениеТоваров.ТоварыОтбор КАК ТоварыОтбор
ГДЕ
ТоварыОтбор.Ссылка.Проведен
И ТоварыОтбор.Распоряжение = ЗНАЧЕНИЕ(Документ.РасходныйОрдерНаТовары.ПустаяСсылка)
И ТоварыОтбор.Ссылка.Распоряжение В (&МассивДокументов)
В данном случае в первой части запроса работает индекс по полю Распоряжение, а во второй — по полю Ссылка. Это позволяет сократить время выполнения с 5 секунд до долей секунды.
Если массив &МассивДокументов достаточно большой, СУБД может тратить время на его обработку внутри условия В. Проанализируем вариант с использованием временных таблиц. Рассмотрим по шагам:
В.
// Сначала подготавливаем документы
ВЫБРАТЬ Ссылка В ВТ_Фильтр ИЗ &МассивДокументов КАК Документы ИНДЕКСИРОВАТЬ ПО Ссылка;
// Затем основной запрос
ВЫБРАТЬ ...
ИЗ Документ.ОтборРазмещениеТоваров.ТоварыОтбор КАК ТоварыОтбор
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Фильтр КАК Фильтр ПО ТоварыОтбор.Распоряжение = Фильтр.Ссылка
ГДЕ ...
Как мы выяснили, обращение к реквизитам через точку (Распоряжение.Ссылка) порождает множество соединений. Если мы точно знаем, что в данном контексте нас интересует только определенный тип документа (например, РасходныйОрдерНаТовары), необходимо использовать оператор ВЫРАЗИТЬ. Это подскажет платформе, с какой конкретно таблицей нужно соединяться, исключая лишние LEFT JOIN.
Рассмотрим пример правильного ограничения типа:
ВЫБРАТЬ
ЕСТЬNULL(ВЫРАЗИТЬ(ТоварыОтбор.Распоряжение КАК Документ.РасходныйОрдерНаТовары).Ссылка,
ТоварыОтбор.Ссылка.Распоряжение) КАК Распоряжение
...
ГДЕ
ТоварыОтбор.Распоряжение ССЫЛКА Документ.РасходныйОрдерНаТовары
Важный нюанс: Даже с ВЫРАЗИТЬ, использование этой конструкции в блоке ГДЕ всё равно может замедлить запрос. Оптимальнее всего выносить такие преобразования в блок ВЫБРАТЬ, а фильтрацию в ГДЕ делать по «чистым» полям.
Выясним еще одну важную деталь: индексируются ли реквизиты табличных частей? В стандартных конфигурациях 1С реквизиты ТЧ по умолчанию не имеют индекса. Если мы выполняем поиск по полю Распоряжение в таблице из 4 миллионов строк, наличие галочки "Индексировать" в свойствах реквизита в конфигураторе является критически важным.
Проанализируем ситуацию: если индекс отсутствует, никакие ухищрения в тексте запроса не заставят его работать мгновенно, так как SQL-серверу в любом случае придется прочитать все 4 миллиона записей с диска.
Для достижения максимальной производительности при работе с большими объемами данных придерживайтесь следующих правил:
ВЫБОР, ЕСТЬNULL или ВЫРАЗИТЬ в условии фильтрации убивает производительность.ОБЪЕДИНИТЬ ВСЕ для разделения логики «Если заполнено / Если не заполнено».ВЫРАЗИТЬ, чтобы ограничить количество таблиц, участвующих в соединениях.NULL.Применяя эти методы, мы превращаем «тяжелый» пятисекундный запрос в эффективный инструмент, который отрабатывает практически незаметно для пользователя.