Как оптимизировать запрос с использованием ОбходРезультатаЗапроса.ПоГруппировкамСИерархией и ускорить заполнение дерева значений

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

При разработке сложных обработок заполнения документов в 1С мы часто сталкиваемся с необходимостью визуализации данных в виде дерева. Использование конструкции ОбходРезультатаЗапроса.ПоГруппировкамСИерархией является стандартным инструментом для решения таких задач, однако оно может стать "узким местом", на которое уходит более 80% времени работы алгоритма. Разберем детально, почему это происходит, как оптимизировать текст запроса и какие административные действия могут кардинально ускорить работу программы.

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

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

Проанализируем пример запроса, который вызывает зависание:


ВЫБРАТЬ РАЗРЕШЕННЫЕ
    ВложенныйЗапрос.ПакетныйЗаказ КАК Пакет,
    ВложенныйЗапрос.Заказ КАК Заказ,
    ВложенныйЗапрос.ГеографическаяПринадлежность КАК ГеографическаяПринадлежность,
    // ... другие поля ...
ИЗ
    (ВЫБРАТЬ
        ЗаявкаПокупателя.Ссылка КАК Заказ,
        МаршрутныйЛистЗаказы.Ссылка КАК МЛ,
        ЗаявкаПокупателя.ГеографическаяПринадлежность КАК ГеографическаяПринадлежность,
        // ... другие поля ...
    ИЗ
        Документ.ЗаявкаПокупателя КАК ЗаявкаПокупателя
            ЛЕВОЕ СОЕДИНЕНИЕ Документ.МаршрутныйЛист.Заказы КАК МаршрутныйЛистЗаказы
            ПО (МаршрутныйЛистЗаказы.Заказ = ЗаявкаПокупателя.Ссылка)
    ГДЕ
        ЗаявкаПокупателя.ДатаЗаказа = &ДатаЗаказа
        И ЗаявкаПокупателя.Проведен = &Проведен
        И НЕ ЗаявкаПокупателя.Ссылка В (&Список)
    ) КАК ВложенныйЗапрос
ГДЕ
    (ВложенныйЗапрос.МЛ ЕСТЬ NULL ИЛИ ВложенныйЗапрос.МЛ = &Ссылка)
УПОРЯДОЧИТЬ ПО
    ГеографическаяПринадлежностьПредставление
ИТОГИ
    СУММА(Вес),
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Заказ)
ПО
    Объект,
    ГеографическаяПринадлежность ИЕРАРХИЯ

В данном запросе мы видим использование итогов по иерархии справочника ГеографическаяПринадлежность. Именно это заставляет платформу выполнять дополнительные действия при построении дерева значений в оперативной памяти.

Оптимизация текста запроса: вложенные запросы и соединения

Рассмотрим основные способы улучшения производительности на уровне кода:

  1. Оптимизация условий соединения. Обратим внимание на конструкцию во внешнем фильтре ВложенныйЗапрос.МЛ ЕСТЬ NULL ИЛИ ВложенныйЗапрос.МЛ = &Ссылка. Фильтры по NULL во внешних запросах часто мешают оптимизатору СУБД использовать индексы. Попробуем перенести условие непосредственно в секцию соединения ПО.
  2. Замена вложенных запросов. Несмотря на то что в некоторых случаях вложенные запросы работают быстро, хорошей практикой считается использование Временных Таблиц (ВТ). Это позволяет СУБД построить более точный план выполнения. Однако, как показывает практика, если данных мало, создание ВТ может добавить накладные расходы на работу с TempDB.
  3. Индексация полей. Если мы используем соединение с табличной частью (Документ.МаршрутныйЛист.Заказы), необходимо убедиться, что поле, по которому идет связь (Заказ), проиндексировано. В типовых конфигурациях это поле обычно является индексируемым, но в самописных объектах на это стоит обратить внимание.

Разберем пример улучшения соединения, предложенный в обсуждении:


// Вместо прямого соединения с табличной частью можно попробовать 
// сначала ограничить выборку самих документов-владельцев
ЛЕВОЕ СОЕДИНЕНИЕ Документ.МаршрутныйЛист КАК МаршрутныйЛист
    ПО МаршрутныйЛист.Дата >= ЗаявкаПокупателя.Дата
СОЕДИНЕНИЕ Документ.МаршрутныйЛист.Заказы КАК МаршрутныйЛистЗаказы
    ПО МаршрутныйЛистЗаказы.Ссылка = МаршрутныйЛист.Ссылка
    И МаршрутныйЛистЗаказы.Заказ = ЗаявкаПокупателя.Ссылка

Этот подход может быть эффективен, если в системе огромное количество Маршрутных Листов, и прямое сканирование их табличных частей обходится слишком дорого.

Механизм иерархии и дерево значений

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

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

Роль тестирования и исправления (ТИИ)

Проанализируем ситуацию, когда код не менялся годами, но внезапно начал тормозить. Как показано в практическом примере, время выполнения запроса сократилось с 25 секунд до 0.2 секунды после проведения штатной процедуры Тестирования и исправления. Почему это сработало?

Основные причины эффективности ТИИ в данном контексте:

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

Итоговые рекомендации по ускорению

  1. Проверьте, действительно ли вам нужна ИЕРАРХИЯ в запросе. Если дерево строится только для визуализации небольшого списка, возможно, быстрее будет выгрузить плоскую таблицу и построить дерево программно.
  2. Убедитесь, что в запросе не используются лишние вложенные уровни без необходимости. По возможности используйте временные таблицы с индексами по полям связи.
  3. Выполните регламентные операции на уровне SQL-сервера: обновление статистик и дефрагментацию индексов.
  4. Если быстродействие упало внезапно — первым делом выполните Тестирование и исправление (хотя бы в части реиндексации и проверки логической целостности).
  5. При использовании платформы 8.3.13 и выше учитывайте, что некоторые релизы имели специфические проблемы с утечкой памяти при работе с иерархическими деревьями. В этом случае может помочь обновление платформы до актуальной версии.

Таким образом, оптимизация иерархических обходов — это комплексная задача, требующая как внимания к тексту запроса, так и контроля за состоянием данных внутри базы.

← На главную