В практике разработчика 1С часто возникают ситуации, когда, казалось бы, простейший запрос к регистру сведений начинает выполняться недопустимо долго или вовсе приводит к «зависанию» системы. Особенно часто это проявляется при работе с механизмом дополнительных сведений в типовых конфигурациях, таких как УТ 11, ERP или КА. В данной статье мы подробно разберем, почему стандартный подход к написанию условий может стать «ловушкой» для производительности, и рассмотрим правильные методы оптимизации.
Рассмотрим ситуацию, когда нам необходимо получить значения дополнительных сведений для документов-оснований конкретного ПорученияЭкспедитору. Типичный код, который часто пишут новички, выглядит так:
ВЫБРАТЬ
ДополнительныеСведения.Объект.Ссылка КАК Ссылка,
ДополнительныеСведения.Свойство КАК Свойство,
ДополнительныеСведения.Значение КАК Значение
ИЗ
РегистрСведений.ДополнительныеСведения КАК ДополнительныеСведения
ГДЕ
ДополнительныеСведения.Объект.Ссылка В
(ВЫБРАТЬ РАЗЛИЧНЫЕ
ПоручениеЭкспедиторуОснования.Основание.Ссылка КАК Ссылка
ИЗ
Документ.ПоручениеЭкспедитору.Основания КАК ПоручениеЭкспедиторуОснования
ГДЕ
ПоручениеЭкспедиторуОснования.Ссылка = &Ссылка)
На первый взгляд запрос кажется логичным, но в нем кроется критическая ошибка — использование обращения через точку .Ссылка к полю Объект. Проанализируем, что происходит на уровне СУБД. В регистре ДополнительныеСведения измерение Объект имеет составной тип данных (практически «ЛюбаяСсылка»). Когда платформа встречает конструкцию Объект.Ссылка, она интерпретирует это как необходимость обратиться к метаданным объекта.
Поскольку тип составной, SQL-оптимизатор вынужден выполнить LEFT JOIN со всеми таблицами, которые потенциально могут входить в этот тип. Если в вашей конфигурации сотни справочников и документов, SQL-текст запроса раздувается до нескольких мегабайт, включая в себя огромное количество соединений. Именно это приводит к «стопору»: СУБД тратит больше времени на построение плана запроса, чем на само получение данных.
Первое и самое простое правило оптимизации: никогда не используйте .Ссылка у полей, которые уже являются ссылочными. Поле Объект в регистре уже содержит в себе GUID ссылки. Обращение Объект.Ссылка не дает новой информации, но заставляет платформу выполнять лишние соединения.
Давайте посмотрим, как изменится время выполнения, если мы просто уберем лишние точки:
ВЫБРАТЬ
ДополнительныеСведения.Объект КАК Объект,
ДополнительныеСведения.Свойство КАК Свойство,
ДополнительныеСведения.Значение КАК Значение
ИЗ
РегистрСведений.ДополнительныеСведения КАК ДополнительныеСведения
ГДЕ
ДополнительныеСведения.Объект В
(ВЫБРАТЬ РАЗЛИЧНЫЕ
ПоручениеЭкспедиторуОснования.Основание
ИЗ
Документ.ПоручениеЭкспедитору.Основания КАК ПоручениеЭкспедиторуОснования
ГДЕ
ПоручениеЭкспедиторуОснования.Ссылка = &Ссылка)
Как показывает практика, такая простая правка может сократить время выполнения с нескольких минут до долей секунды (в обсуждении на форуме зафиксировано ускорение до 0,012с).
Хотя удаление .Ссылка помогает, использование вложенного запроса в операторе В все еще может быть неоптимальным при больших объемах данных. Разберем более профессиональный подход с использованием временных таблиц. Это позволяет разделить сложную задачу на простые этапы, понятные для оптимизатора СУБД.
Выполним оптимизацию по шагам:
// Шаг 1: Подготовка оснований
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПоручениеЭкспедиторуОснования.Основание КАК Основание
ПОМЕСТИТЬ ВТ_Основания
ИЗ
Документ.ПоручениеЭкспедитору.Основания КАК ПоручениеЭкспедиторуОснования
ГДЕ
ПоручениеЭкспедиторуОснования.Ссылка = &Ссылка
ИНДЕКСИРОВАТЬ ПО
Основание;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Получение доп. сведений через соединение
ВЫБРАТЬ
ВТ_Основания.Основание КАК Ссылка,
ДополнительныеСведения.Свойство КАК Свойство,
ДополнительныеСведения.Значение КАК Значение
ИЗ
ВТ_Основания КАК ВТ_Основания
ВНУТРЕННЕЕ СОЕДИНЕНИЕ
РегистрСведений.ДополнительныеСведения КАК ДополнительныеСведения
ПО
ДополнительныеСведения.Объект = ВТ_Основания.Основание
Почему это работает быстрее? Использование временной таблицы ВТ_Основания жестко типизирует данные. СУБД больше не нужно гадать, какие типы могут встретиться в условии — она работает с конкретным набором ссылок. Оператор ВНУТРЕННЕЕ СОЕДИНЕНИЕ (Inner Join) в данном случае отработает максимально эффективно, используя индексы регистра.
Рассмотрим важный нюанс, упомянутый в обсуждении. Если ваша задача — получить сведения только для тех объектов, у которых они заполнены, используйте ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Однако, если вам нужно вывести список всех оснований, даже если для них не заданы дополнительные сведения, необходимо использовать ЛЕВОЕ СОЕДИНЕНИЕ.
При использовании левого соединения важно правильно обрабатывать NULL значения, которые появятся в полях Свойство и Значение для пустых записей. Проанализируем пример кода:
ВЫБРАТЬ
ВТ_Основания.Основание КАК Ссылка,
ЕСТЬNULL(ДополнительныеСведения.Свойство, ЗНАЧЕНИЕ(ПланВидовХарактеристик.ДополнительныеСведенияИСведения.ПустаяСсылка)) КАК Свойство,
ДополнительныеСведения.Значение КАК Значение
ИЗ
ВТ_Основания КАК ВТ_Основания
ЛЕВОЕ СОЕДИНЕНИЕ
РегистрСведений.ДополнительныеСведения КАК ДополнительныеСведения
ПО
ДополнительныеСведения.Объект = ВТ_Основания.Основание
В ходе оптимизации часто возникает вопрос: что использовать для получения уникальных записей — ВЫБРАТЬ РАЗЛИЧНЫЕ (Distinct) или СГРУППИРОВАТЬ ПО (Group By)? С точки зрения большинства современных СУБД (SQL Server, PostgreSQL), эти инструкции транслируются в идентичные планы выполнения. Однако в среде 1С принято считать, что СГРУППИРОВАТЬ ПО является более явным и гибким инструментом, особенно если в будущем потребуется добавить агрегатные функции (например, количество доп. сведений на один объект).
Выясним причину, по которой даже оптимизированный запрос может работать медленно. Проверьте наличие индексов. В типовых конфигурациях регистр ДополнительныеСведения уже хорошо проиндексирован по измерениям Объект и Свойство. Однако, если вы создаете свои таблицы или документы, убедитесь, что поле, по которому идет связь (в нашем примере Основание в табличной части), включено в индекс. Хотя отсутствие индекса не всегда «вешает» систему, в сочетании с составными типами это создает критическую нагрузку на сервер.
Подведем итоги нашего анализа. Чтобы ваши запросы к дополнительным сведениям (и любым другим регистрам с составными измерениями) работали мгновенно, придерживайтесь следующих правил:
ГДЕ и ПО.ИНДЕКСИРОВАТЬ ПО.Index Scan всей таблицы вместо Index Seek, значит, СУБД не может эффективно использовать индексы из-за неявного приведения типов или сложных соединений.Соблюдение этих простых, но фундаментальных принципов разработки на платформе 1С позволит вам создавать высокопроизводительные системы, устойчивые к росту объемов данных.