В практике разработки на платформе «1С:Предприятие 8» часто возникает соблазн написать «быстрый» запрос, который кажется логичным, но с точки зрения производительности СУБД является крайне неэффективным. Рассмотрим классический пример кода, который вызывает бурные дискуссии на форумах, и разберем, почему его называют «г-кодом», а также как превратить его в профессиональное решение.
Рассмотрим исходный запрос, который вызвал обсуждение:
ВЫБРАТЬ
Т.Ссылка
ИЗ
Справочник.Контрагенты КАК Т
ГДЕ
Т.Менеджер = (&Менеджер)
ИЛИ Т.Менеджер = (&Менеджер).Родитель
На первый взгляд, запрос прост и понятен: мы хотим найти всех контрагентов, за которыми закреплен конкретный менеджер или его непосредственный руководитель (родитель в иерархии). Однако в этом коротком фрагменте скрыто сразу две серьезные проблемы производительности и архитектуры. Проанализируем их пошагово.
Когда мы пишем в условии (&Менеджер).Родитель, платформа 1С не может просто подставить значение в SQL-запрос. Для СУБД параметр — это просто ссылка (GUID). Чтобы узнать, кто является родителем этой ссылки, платформе приходится выполнять неявное соединение (LEFT JOIN) с таблицей справочника менеджеров прямо внутри запроса.
Чем это плохо?
Это «золотое правило» оптимизации 1С: оператор ИЛИ в условии ГДЕ — это потенциальная угроза производительности. Рассмотрим ситуацию глазами SQL-сервера.
Для эффективной работы СУБД использует индексы. Если у вас есть условие по полю Менеджер, SQL-сервер хочет выполнить операцию Index Seek (быстрый поиск по дереву индекса). Однако наличие ИЛИ часто заставляет оптимизатор отказаться от использования индексов и перейти к Index Scan или даже Table Scan (полному сканированию таблицы). На справочнике в 100 элементов это незаметно, но на боевых данных в сотни тысяч записей запрос начнет «тормозить», вызывая избыточные блокировки.
Самый правильный и «чистый» способ — подготовить все необходимые значения заранее в коде на языке 1С и передать их в запрос в виде массива. Разберем, как это реализовать:
// 1. Получаем родителя заранее.
// Лучше использовать функции общего назначения, чтобы избежать лишних обращений к базе.
РодительМенеджера = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ПараметрМенеджер, "Родитель");
// 2. Формируем массив со всеми значениями, которые участвуют в фильтре
МассивМенеджеров = Новый Массив;
МассивМенеджеров.Добавить(ПараметрМенеджер);
Если ЗначениеЗаполнено(РодительМенеджера) Тогда
МассивМенеджеров.Добавить(РодительМенеджера);
КонецЕсли;
// 3. Передаем массив в запрос и используем оператор "В"
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Ссылка
|ИЗ
| Справочник.Контрагенты КАК Т
|ГДЕ
| Т.Менеджер В (&МассивМенеджеров)";
Запрос.УстановитьПараметр("МассивМенеджеров", МассивМенеджеров);
Результат = Запрос.Выполнить();
Почему это работает лучше?
Во-первых, мы избавились от ИЛИ, заменив его оператором В. Для СУБД это более прозрачное условие. Во-вторых, мы убрали обращение через точку. Теперь в запрос передаются конкретные значения ссылок, и SQL-серверу остается только выполнить эффективный поиск по индексу Менеджер.
Если по каким-то причинам вы не можете подготовить массив заранее (например, условие ИЛИ связывает совершенно разные поля), следует использовать объединение запросов. Рассмотрим этот метод:
ВЫБРАТЬ
Т.Ссылка
ИЗ
Справочник.Контрагенты КАК Т
ГДЕ
Т.Менеджер = &Менеджер
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Т.Ссылка
ИЗ
Справочник.Контрагенты КАК Т
ГДЕ
Т.Менеджер = &РодительМенеджера
Проанализируем ситуацию: в данном случае СУБД выполнит два независимых и очень быстрых поиска по индексу (Index Seek) и просто склеит результаты. Это гарантирует стабильную скорость даже на огромных таблицах. Стоит отметить, что при использовании ОБЪЕДИНИТЬ ВСЕ могут возникнуть дубли, если один и тот же контрагент удовлетворяет обоим условиям (хотя в нашем примере с иерархией это маловероятно). Если дубли критичны, используйте просто ОБЪЕДИНИТЬ, но помните, что оно работает чуть медленнее из-за операции сравнения строк.
Выясним причину, почему это особенно важно для систем на Linux и Postgres. В отличие от MS SQL Server, который имеет довольно продвинутый оптимизатор, PostgreSQL более чувствителен к конструкции ИЛИ. В старых версиях или при определенных настройках Postgres может полностью игнорировать индексы в таких запросах. Переход на UNION ALL или использование оператора В с массивом констант для PostgreSQL является обязательным стандартом разработки.
Рассмотрим краткий чек-лист для проверки ваших запросов:
&Параметр.Поле). Получите значение в коде и передайте в запрос готовый результат.В и передайте массив. Если поля разные — используйте ОБЪЕДИНИТЬ ВСЕ.Соблюдение этих простых правил отличает профессиональную разработку от «г-кода» и гарантирует, что ваша база данных не «ляжет» при увеличении объема данных в несколько раз.