Как оптимизировать запрос к регистру сведений с составными типами и условием ИЛИ?

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

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

В чем заключается проблема использования ИЛИ в запросах

Проанализируем классическую ситуацию: у нас есть регистр сведений, в котором Поле1 и Поле2 проиндексированы и имеют составной тип. Мы пытаемся выбрать данные, где хотя бы одно из полей соответствует списку документов. На первый взгляд, запрос выглядит просто:


ВЫБРАТЬ
    РегистрСведений.Поле1,
    РегистрСведений.Поле2
ИЗ
    РегистрСведений.НазваниеРегистра КАК РегистрСведений
ГДЕ
    (РегистрСведений.Поле1 В (&СписокДокументов)
        ИЛИ РегистрСведений.Поле2 В (&СписокДокументов))

Проблема кроется в том, как SQL-оптимизатор обрабатывает оператор OR. Когда в условии ГДЕ стоят разные поля, соединенные через ИЛИ, СУБД зачастую не может использовать индексы эффективно. Вместо быстрого поиска по индексу (Index Seek), система может переключиться на полное сканирование таблицы (Index Scan), что при большом объеме данных в регистре приводит к колоссальным задержкам.

Решение №1: Замена условия ИЛИ на ОБЪЕДИНИТЬ ВСЕ

Рассмотрим самый действенный способ оптимизации — разделение одного запроса на несколько и их последующее объединение. Это позволяет СУБД построить отдельный план выполнения для каждого куска запроса, используя соответствующий индекс для каждого поля.

Разберем пример реализации этого подхода:


ВЫБРАТЬ
    РегистрСведений.Поле1 КАК Поле1,
    РегистрСведений.Поле2 КАК Поле2
ИЗ
    РегистрСведений.НазваниеРегистра КАК РегистрСведений
ГДЕ
    РегистрСведений.Поле1 В (&СписокДокументов)

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

ВЫБРАТЬ
    РегистрСведений.Поле1,
    РегистрСведений.Поле2
ИЗ
    РегистрСведений.НазваниеРегистра КАК РегистрСведений
ГДЕ
    РегистрСведений.Поле2 В (&СписокДокументов)

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

Решение №2: Оптимизация работы с составными типами через ВЫРАЗИТЬ

Выясним причину, почему составные типы данных так "тяжелы" для системы. В базе данных MS SQL поле составного типа хранится в нескольких колонках (идентификатор типа, ссылка в виде GUID и т.д.). Когда мы пишем В (&СписокДокументов), платформа генерирует сложный SQL-код, который пытается сопоставить типы из списка с типами в поле регистра.

Если мы заранее знаем, что в списке &СписокДокументов находятся ссылки только одного типа (например, только "Реализация товаров и услуг"), мы можем значительно упростить задачу SQL-серверу, применив оператор ВЫРАЗИТЬ. Проанализируем пример:


ВЫБРАТЬ
    РегистрСведений.Поле1,
    РегистрСведений.Поле2
ИЗ
    РегистрСведений.НазваниеРегистра КАК РегистрСведений
ГДЕ
    ВЫРАЗИТЬ(РегистрСведений.Поле1 КАК Документ.РеализацияТоваровУслуг) В (&СписокДокументов)

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

Решение №3: Использование временных таблиц для списков документов

Посмотрим на ситуацию, когда список документов в параметре &СписокДокументов достаточно велик (более 20-50 значений). Передача большого массива напрямую в условие В (...) может быть неэффективной. В этом случае правильнее будет сначала поместить эти ссылки во временную таблицу.

Разберем по шагам, как это сделать:

  1. Создаем временную таблицу ВТ_СписокДокументов.
  2. Индексируем поле со ссылкой во временной таблице.
  3. Выполняем ВНУТРЕННЕЕ СОЕДИНЕНИЕ регистра с этой таблицей вместо использования условия ГДЕ ... В (...).

Пример такого запроса:


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

// Основной запрос
ВЫБРАТЬ
    РегистрСведений.Поле1,
    РегистрСведений.Поле2
ИЗ
    РегистрСведений.НазваниеРегистра КАК РегистрСведений
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_СписокДокументов КАК ВТ
    ПО РегистрСведений.Поле1 = ВТ.ДокСсылка

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

ВЫБРАТЬ
    РегистрСведений.Поле1,
    РегистрСведений.Поле2
ИЗ
    РегистрСведений.НазваниеРегистра КАК РегистрСведений
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_СписокДокументов КАК ВТ
    ПО РегистрСведений.Поле2 = ВТ.ДокСсылка

Такой подход позволяет SQL-серверу использовать наиболее оптимальный алгоритм соединения (например, Nested Loops или Hash Match), основываясь на статистике данных во временной таблице.

Дополнительные рекомендации по архитектуре

Помимо оптимизации самих запросов, стоит обратить внимание на структуру метаданных. Рассмотрим несколько советов:

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

← На главную