Оптимизация запросов 1С: почему нельзя использовать ИЛИ и обращение через точку от параметра

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

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

Анализ исходной проблемы

Рассмотрим исходный запрос, который вызвал обсуждение:


ВЫБРАТЬ 
    Т.Ссылка
ИЗ
    Справочник.Контрагенты КАК Т
ГДЕ 
     Т.Менеджер = (&Менеджер)
     ИЛИ Т.Менеджер = (&Менеджер).Родитель

На первый взгляд, запрос прост и понятен: мы хотим найти всех контрагентов, за которыми закреплен конкретный менеджер или его непосредственный руководитель (родитель в иерархии). Однако в этом коротком фрагменте скрыто сразу две серьезные проблемы производительности и архитектуры. Проанализируем их пошагово.

Проблема №1: Обращение через точку от параметра (&Параметр).Поле

Когда мы пишем в условии (&Менеджер).Родитель, платформа 1С не может просто подставить значение в SQL-запрос. Для СУБД параметр — это просто ссылка (GUID). Чтобы узнать, кто является родителем этой ссылки, платформе приходится выполнять неявное соединение (LEFT JOIN) с таблицей справочника менеджеров прямо внутри запроса.

Чем это плохо?

  1. Неявные соединения: Если параметр имеет составной тип, платформа создаст соединения со всеми таблицами, входящими в этот тип. Это раздувает SQL-текст и путает оптимизатор СУБД.
  2. Ограничения конструктора: Как справедливо замечено в обсуждении, конструктор запросов 1С часто «ругается» на такие конструкции, так как они нарушают логику разделения данных.
  3. Лишняя работа СУБД: Вместо того чтобы получить готовое значение, сервер базы данных вынужден для каждой строки основной таблицы выполнять поиск в связанной таблице, чтобы проверить условие.

Проблема №2: Оператор ИЛИ (OR) в секции ГДЕ

Это «золотое правило» оптимизации 1С: оператор ИЛИ в условии ГДЕ — это потенциальная угроза производительности. Рассмотрим ситуацию глазами SQL-сервера.

Для эффективной работы СУБД использует индексы. Если у вас есть условие по полю Менеджер, SQL-сервер хочет выполнить операцию Index Seek (быстрый поиск по дереву индекса). Однако наличие ИЛИ часто заставляет оптимизатор отказаться от использования индексов и перейти к Index Scan или даже Table Scan (полному сканированию таблицы). На справочнике в 100 элементов это незаметно, но на боевых данных в сотни тысяч записей запрос начнет «тормозить», вызывая избыточные блокировки.

Решение №1: Использование массива параметров (оптимальный вариант)

Самый правильный и «чистый» способ — подготовить все необходимые значения заранее в коде на языке 1С и передать их в запрос в виде массива. Разберем, как это реализовать:


// 1. Получаем родителя заранее. 
// Лучше использовать функции общего назначения, чтобы избежать лишних обращений к базе.
РодительМенеджера = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ПараметрМенеджер, "Родитель");

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

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

Запрос.УстановитьПараметр("МассивМенеджеров", МассивМенеджеров);
Результат = Запрос.Выполнить();

Почему это работает лучше?

Во-первых, мы избавились от ИЛИ, заменив его оператором В. Для СУБД это более прозрачное условие. Во-вторых, мы убрали обращение через точку. Теперь в запрос передаются конкретные значения ссылок, и SQL-серверу остается только выполнить эффективный поиск по индексу Менеджер.

Решение №2: Использование ОБЪЕДИНИТЬ ВСЕ (UNION ALL)

Если по каким-то причинам вы не можете подготовить массив заранее (например, условие ИЛИ связывает совершенно разные поля), следует использовать объединение запросов. Рассмотрим этот метод:


ВЫБРАТЬ
    Т.Ссылка
ИЗ
    Справочник.Контрагенты КАК Т
ГДЕ
    Т.Менеджер = &Менеджер

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

ВЫБРАТЬ
    Т.Ссылка
ИЗ
    Справочник.Контрагенты КАК Т
ГДЕ
    Т.Менеджер = &РодительМенеджера

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

Особенности для систем на PostgreSQL

Выясним причину, почему это особенно важно для систем на Linux и Postgres. В отличие от MS SQL Server, который имеет довольно продвинутый оптимизатор, PostgreSQL более чувствителен к конструкции ИЛИ. В старых версиях или при определенных настройках Postgres может полностью игнорировать индексы в таких запросах. Переход на UNION ALL или использование оператора В с массивом констант для PostgreSQL является обязательным стандартом разработки.

Подведем итоги: как писать правильно

Рассмотрим краткий чек-лист для проверки ваших запросов:

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

← На главную