Эквивалентны ли условия ИЛИ и ВЫБОР КОГДА в запросах 1С и как это влияет на скорость

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

В практике разработки на платформе 1С программисты часто сталкиваются с необходимостью построения гибких запросов, которые должны учитывать наличие или отсутствие определенного параметра. Типичный пример — фильтрация по спецификации: если параметр заполнен, выбираем данные по нему, если нет — используем основную спецификацию. На первый взгляд, такие условия кажутся одинаковыми, однако с точки зрения производительности СУБД разница может быть существенной. Проанализируем ситуацию подробно.

Разбор исходных условий

Рассмотрим два варианта написания условия ГДЕ, которые предложил автор темы:

Вариант 1: Использование классической логики И/ИЛИ


&Спецификация = НЕОПРЕДЕЛЕНО 
И РесурсныеСпецификацииВыходныеИзделия.Ссылка = ОсновныеСпецификации.Спецификация 
ИЛИ &Спецификация = РесурсныеСпецификацииВыходныеИзделия.Ссылка

Вариант 2: Использование конструкции ВЫБОР КОГДА


ВЫБОР 
    КОГДА &Спецификация = НЕОПРЕДЕЛЕНО 
    ТОГДА РесурсныеСпецификацииВыходныеИзделия.Ссылка = ОсновныеСпецификации.Спецификация
    ИНАЧЕ &Спецификация = РесурсныеСпецификацииВыходныеИзделия.Ссылка
КОНЕЦ

С точки зрения формальной логики эти конструкции эквивалентны. В обоих случаях, если параметр &Спецификация не задан, сработает первое условие сравнения ссылок. Если параметр задан — второе. Однако то, как эти условия будут преобразованы в SQL-код и как их обработает оптимизатор базы данных, — ключевой вопрос производительности.

Проблема производительности: SARGability и индексы

Основная причина медленной работы подобных запросов на больших таблицах заключается в потере SARGability (Search Argumentable). СУБД эффективно работает тогда, когда может применить Index Seek (поиск по индексу). Когда мы объединяем условия через ИЛИ или используем CASE (в который транслируется ВЫБОР) внутри блока WHERE, оптимизатор SQL Server часто не может заранее определить, какую часть индекса использовать.

Проанализируем, что происходит при выполнении:

  1. Сканирование вместо поиска: Вместо быстрого перехода к нужной записи, СУБД может выбрать Index Scan (полное сканирование индекса) или даже Table Scan, так как логика условия зависит от значения внешнего параметра, который может меняться.
  2. Ленивые вычисления: Существует мнение, что СУБД вычисляет условия «лениво» (слева направо до первого совпадения). В языке 1С это действительно так, но в SQL-стандарте порядок вычислений в блоке WHERE не гарантирован. Оптимизатор может сначала проверить вторую часть ИЛИ, если посчитает это более выгодным согласно статистике, что иногда приводит к неожиданным замедлениям.
  3. Parameter Sniffing: При первом запуске запроса СУБД создает план выполнения. Если первый запуск был с пустым параметром, план будет оптимизирован под полное сканирование. При последующем запуске с конкретной ссылкой СУБД попытается использовать тот же неэффективный план.

Сравнение ИЛИ против ВЫБОР КОГДА

Как показывает практика и опыт разработчиков крупных систем (например, ERP или УТ 11), конструкция ВЫБОР КОГДА часто ведет себя чуть стабильнее. Это связано с тем, что CASE в SQL более жестко определяет логическую ветку. Однако на действительно больших объемах данных (миллионы строк) оба варианта могут «тормозить», если оптимизатор решит, что универсальный план лучше, чем риск использования неподходящего индекса.

Решение 1: Динамическое формирование текста запроса

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


ТекстУсловия = "";
Если ЗначениеЗаполнено(ПараметрСпецификация) Тогда
    ТекстУсловия = "ГДЕ РесурсныеСпецификацииВыходныеИзделия.Ссылка = &Спецификация";
Иначе
    ТекстУсловия = "ГДЕ РесурсныеСпецификацииВыходныеИзделия.Ссылка = ОсновныеСпецификации.Спецификация";
КонецЕсли;

Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ... ИЗ ... " + ТекстУсловия;
Запрос.УстановитьПараметр("Спецификация", ПараметрСпецификация);

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

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

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


ВЫБРАТЬ ...
ГДЕ &Спецификация <> НЕОПРЕДЕЛЕНО 
  И РесурсныеСпецификацииВыходныеИзделия.Ссылка = &Спецификация

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

ВЫБРАТЬ ...
ГДЕ &Спецификация = НЕОПРЕДЕЛЕНО 
  И РесурсныеСпецификацииВыходныеИзделия.Ссылка = ОсновныеСпецификации.Спецификация

Несмотря на то что кода становится больше, для SQL Server это выглядит как два независимых простых поиска. Оптимизатор легко отрабатывает каждую часть, и общая скорость выполнения будет значительно выше, чем при использовании ИЛИ.

Резюме

Подведем итоги нашего анализа:

Использование правильных конструкций в Запрос — это залог масштабируемости вашей системы. Даже если сейчас данных мало и разница незаметна, при росте базы неправильное условие ГДЕ может стать критической точкой отказа.

← На главную