При разработке сложных обработок заполнения документов в 1С мы часто сталкиваемся с необходимостью визуализации данных в виде дерева. Использование конструкции ОбходРезультатаЗапроса.ПоГруппировкамСИерархией является стандартным инструментом для решения таких задач, однако оно может стать "узким местом", на которое уходит более 80% времени работы алгоритма. Разберем детально, почему это происходит, как оптимизировать текст запроса и какие административные действия могут кардинально ускорить работу программы.
Рассмотрим ситуацию, когда выполнение метода Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией) занимает десятки секунд при относительно небольшом объеме данных (чуть более 100 документов). В подобных случаях замер производительности четко указывает на строку выгрузки результата, но корень проблемы часто кроется глубже: в структуре самого запроса и состоянии базы данных.
Проанализируем пример запроса, который вызывает зависание:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ВложенныйЗапрос.ПакетныйЗаказ КАК Пакет,
ВложенныйЗапрос.Заказ КАК Заказ,
ВложенныйЗапрос.ГеографическаяПринадлежность КАК ГеографическаяПринадлежность,
// ... другие поля ...
ИЗ
(ВЫБРАТЬ
ЗаявкаПокупателя.Ссылка КАК Заказ,
МаршрутныйЛистЗаказы.Ссылка КАК МЛ,
ЗаявкаПокупателя.ГеографическаяПринадлежность КАК ГеографическаяПринадлежность,
// ... другие поля ...
ИЗ
Документ.ЗаявкаПокупателя КАК ЗаявкаПокупателя
ЛЕВОЕ СОЕДИНЕНИЕ Документ.МаршрутныйЛист.Заказы КАК МаршрутныйЛистЗаказы
ПО (МаршрутныйЛистЗаказы.Заказ = ЗаявкаПокупателя.Ссылка)
ГДЕ
ЗаявкаПокупателя.ДатаЗаказа = &ДатаЗаказа
И ЗаявкаПокупателя.Проведен = &Проведен
И НЕ ЗаявкаПокупателя.Ссылка В (&Список)
) КАК ВложенныйЗапрос
ГДЕ
(ВложенныйЗапрос.МЛ ЕСТЬ NULL ИЛИ ВложенныйЗапрос.МЛ = &Ссылка)
УПОРЯДОЧИТЬ ПО
ГеографическаяПринадлежностьПредставление
ИТОГИ
СУММА(Вес),
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Заказ)
ПО
Объект,
ГеографическаяПринадлежность ИЕРАРХИЯ
В данном запросе мы видим использование итогов по иерархии справочника ГеографическаяПринадлежность. Именно это заставляет платформу выполнять дополнительные действия при построении дерева значений в оперативной памяти.
Рассмотрим основные способы улучшения производительности на уровне кода:
ВложенныйЗапрос.МЛ ЕСТЬ NULL ИЛИ ВложенныйЗапрос.МЛ = &Ссылка. Фильтры по NULL во внешних запросах часто мешают оптимизатору СУБД использовать индексы. Попробуем перенести условие непосредственно в секцию соединения ПО.TempDB.Документ.МаршрутныйЛист.Заказы), необходимо убедиться, что поле, по которому идет связь (Заказ), проиндексировано. В типовых конфигурациях это поле обычно является индексируемым, но в самописных объектах на это стоит обратить внимание.Разберем пример улучшения соединения, предложенный в обсуждении:
// Вместо прямого соединения с табличной частью можно попробовать
// сначала ограничить выборку самих документов-владельцев
ЛЕВОЕ СОЕДИНЕНИЕ Документ.МаршрутныйЛист КАК МаршрутныйЛист
ПО МаршрутныйЛист.Дата >= ЗаявкаПокупателя.Дата
СОЕДИНЕНИЕ Документ.МаршрутныйЛист.Заказы КАК МаршрутныйЛистЗаказы
ПО МаршрутныйЛистЗаказы.Ссылка = МаршрутныйЛист.Ссылка
И МаршрутныйЛистЗаказы.Заказ = ЗаявкаПокупателя.Ссылка
Этот подход может быть эффективен, если в системе огромное количество Маршрутных Листов, и прямое сканирование их табличных частей обходится слишком дорого.
Выясним причину, почему ИЕРАРХИЯ в итогах может тормозить процесс. Когда платформа 1С встречает инструкцию ПО Поле ИЕРАРХИЯ, она должна построить полную цепочку родителей для каждого элемента, попавшего в результат. Если в иерархическом справочнике тысячи элементов или большая глубина вложенности, сервер приложений 1С начинает выполнять множество неявных запросов к базе данных для получения информации о вышестоящих узлах.
Важный нюанс: метод Выгрузить() пытается создать всю структуру дерева в памяти целиком. При больших объемах данных это вызывает фрагментацию памяти и задержки. Если оптимизация запроса не помогает, рекомендуем рассмотреть замену Выгрузить() на последовательный обход Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией) с ручным заполнением дерева.
Проанализируем ситуацию, когда код не менялся годами, но внезапно начал тормозить. Как показано в практическом примере, время выполнения запроса сократилось с 25 секунд до 0.2 секунды после проведения штатной процедуры Тестирования и исправления. Почему это сработало?
Основные причины эффективности ТИИ в данном контексте:
Index Scan вместо Index Seek). ТИИ принудительно обновляет эти структуры.Родитель указана несуществующая ссылка или возникла циклическая зависимость, алгоритм построения иерархии может уйти в бесконечный цикл или вызывать ошибки СУБД типа Cannot insert the value NULL.В рассматриваемом случае в процессе ТИИ была обнаружена ошибка в справочнике СопроводительныеДокументы, связанная с невозможностью вставки NULL в колонку. Это явный признак того, что в базе существовали некорректные записи, которые мешали нормальной работе механизмов платформы.
ИЕРАРХИЯ в запросе. Если дерево строится только для визуализации небольшого списка, возможно, быстрее будет выгрузить плоскую таблицу и построить дерево программно.Таким образом, оптимизация иерархических обходов — это комплексная задача, требующая как внимания к тексту запроса, так и контроля за состоянием данных внутри базы.