Почему медленно работает запрос с конструкцией В ИЕРАРХИИ в 1С и как его ускорить?

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

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

Выясним причину: как платформа 1С обрабатывает «В ИЕРАРХИИ» на уровне СУБД

Для начала проанализируем ситуацию изнутри. В классическом языке SQL нет прямого эквивалента оператору В ИЕРАРХИИ. Чтобы транслировать этот запрос в СУБД, платформа 1С:Предприятие выполняет скрытую рекурсивную процедуру:

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

Если глубина иерархии велика, а справочник содержит сотни тысяч элементов (например, номенклатура автозапчастей), этот процесс порождает огромное количество дисковых и вычислительных операций. Ситуация усугубляется в файловом варианте базы данных (формат .1cd), где встроенный СУБД-движок лишен продвинутого оптимизатора планов запросов, эффективного кэширования и возможностей распараллеливания вычислений.

Опасная ловушка: Если в качестве параметра &Ссылка случайно передается пустая ссылка справочника (например, Справочники.Номенклатура.ПустаяСсылка()), СУБД пытается построить иерархическое дерево для всей таблицы, выполняя полное сканирование (Full Table Scan). Это гарантированно приводит к зависанию сеанса.

Перейдем к практическим методам решения этой проблемы.

Способ 1. Фильтрация по непосредственному родителю (самый простой путь)

Проанализируем бизнес-требования к задаче. Часто разработчики используют В ИЕРАРХИИ по привычке, хотя пользователю в интерфейсе требуется видеть только элементы, лежащие непосредственно внутри конкретной папки, без учета вложенных подпапок.

Если это ваш случай, откажитесь от тяжелого рекурсивного поиска. Достаточно использовать отбор по равенству родителю:


ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    Номенклатура.Родитель = &Родитель
    И Номенклатура.ЭтоГруппа = ЛОЖЬ

Такой запрос отрабатывает за доли миллисекунды, так как реквизит Родитель в 1С проиндексирован по умолчанию.

Способ 2. Оптимизация работы с Динамическими списками (СКД)

Если запрос используется в качестве основного текста для Динамического списка в управляемой форме (например, в форме подбора товара), крайне не рекомендуется жестко зашивать параметры в текст запроса.

Система компоновки данных (СКД), которая управляет динамическими списками, умеет оптимизировать и кэшировать иерархические отборы гораздо эффективнее, чем ручной запрос с параметром. Разберем по шагам правильный алгоритм настройки формы:

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

&НаСервере
Процедура УстановитьИерархическийОтборНаСервере(ВыбраннаяГруппа)
    
    // Очищаем старые отборы по родителю
    ГруппаОтбора = СписокНоменклатуры.Отбор.Элементы;
    Для Каждого ЭлементОтбора Из ГруппаОтбора Цикл
        Если ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка") Тогда
            ГруппаОтбора.Удалить(ЭлементОтбора);
        КонецЕсли;
    КонецЦикла;
    
    // Если передана непустая папка, устанавливаем иерархический отбор
    Если ЗначениеЗаполнено(ВыбраннаяГруппа) Тогда
        НовыйОтбор = ГруппаОтбора.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
        НовыйОтбор.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка");
        НовыйОтбор.ВидСравнения = ВидСравненияКомпоновкиДанных.ВИерархии;
        НовыйОтбор.ПравоеЗначение = ВыбраннаяГруппа;
        НовыйОтбор.Использование = Истина;
    КонецЕсли;

КонецПроцедуры

При таком подходе платформа сама оптимизирует передачу данных между сервером и клиентом, а СУБД строит более эффективный план выполнения за счет внутренних механизмов СКД.

Способ 3. Создание вспомогательного регистра сведений (Транзитивное замыкание)

Это профессиональное архитектурное решение, которое используется в крупных типовых конфигурациях (например, регистр "Иерархия партнеров" в ERP и Управлении торговлей). Мы создаем плоскую таблицу связей "Предмет — Все его предки". Это позволяет полностью исключить рекурсию при поиске.

Рассмотрим подробнее реализацию этого метода.

Шаг 1. Создание структуры регистра

Создадим новый независимый непериодический регистр сведений ИерархияНоменклатуры со следующими реквизитами:

Шаг 2. Заполнение регистра при записи элемента

Необходимо организовать подписку на событие или прописать логику в модуле объекта справочника Номенклатура в процедуре ПриЗаписи. При сохранении элемента мы должны пересчитать его цепочку родителей и записать в регистр. Посмотрим на алгоритм заполнения:


Процедура ОбновитьИерархиюЭлемента(ТекущийОбъект) Экспорт
    
    СсылкаНаОбъект = ТекущийОбъект.Ссылка;
    
    // Начинаем с удаления старых записей для этого элемента
    НаборЗаписей = РегистрыСведений.ИерархияНоменклатуры.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Номенклатура.Установить(СсылкаНаОбъект);
    НаборЗаписей.Записать(); // Очистка
    
    // Если объект сам является группой и у него нет родителя (корень)
    Если Не ЗначениеЗаполнено(ТекущийОбъект.Родитель) Тогда
        Возврат;
    КонецЕсли;
    
    // Двигаемся вверх по дереву и собираем всех родителей
    ТекущийРодитель = ТекущийОбъект.Roditel; // Обратите внимание на корректность обращения к реквизиту Родитель
    ТекущийРодитель = ТекущийОбъект.Родитель;
    ТекущийУровень = 1;
    
    Пока ЗначениеЗаполнено(ТекущийРодитель) Цикл
        
        // Создаем новую запись в регистре
        НоваяЗапись = НаборЗаписей.Добавить();
        НоваяЗапись.Номенклатура = СсылкаНаОбъект;
        НоваяЗапись.Родитель = ТекущийРодитель;
        НоваяЗапись.Уровень = ТекущийУровень;
        
        // Переходим к родителю следующего уровня
        ТекущийРодитель = ТекущийРодитель.Родитель;
        ТекущийУровень = ТекущийУровень + 1;
        
    КонецЦикла;
    
    НаборЗаписей.Записать(Ложь); // Записываем новые связи
    
КонецПроцедуры

Шаг 3. Использование регистра в запросах

Теперь мы можем полностью отказаться от конструкции В ИЕРАРХИИ. Заменим тяжелое условие на быструю плоскую выборку с использованием соединения:


ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    Номенклатура.Ссылка В (
        ВЫБРАТЬ
            Иерархия.Номенклатура
        ИЗ
            РегистрСведений.ИерархияНоменклатуры КАК Иерархия
        ГДЕ
            Иерархия.Родитель = &Родитель
    )
    И Номенклатура.ЭтоГруппа = ЛОЖЬ

Преимущества данного метода: Скорость выполнения запроса снижается с 21 секунды до 0.005 секунды. База данных использует индекс по измерению Родитель регистра сведений, мгновенно получая список подходящих ссылок.

Способ 4. Обновление технологической платформы 1С

Если вы вынуждены использовать старые версии платформы (например, релизы линейки 8.3.13), помните, что в них присутствовали системные проблемы с оптимизацией рекурсивных SQL-запросов под управлением файлового движка DB.

Начиная с версии платформы 8.3.21, разработчики фирмы 1С провели глобальную оптимизацию трансляции оператора В ИЕРАРХИИ. Если у вас есть возможность перевести базу данных на современную версию платформы, сделайте это. В большинстве случаев скорость выполнения подобных запросов "из коробки" возрастает в несколько раз без изменения строчек кода конфигурации.

← На главную