Разыменование полей через точку против явного соединения в запросах 1С: подробный анализ производительности

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

В сообществе разработчиков 1С не утихают споры о том, как правильно и максимально эффективно получать реквизиты связанных объектов в запросах. Существует популярное мнение, что обращение через точку (неявное соединение) работает медленнее, чем явное левое или внутреннее соединение (LEFT JOIN / INNER JOIN). В рамках данной статьи мы подробно разберем этот вопрос, проанализируем реальные планы SQL-запросов на миллионных объемах данных, выясним влияние статистики СУБД и определим, когда использование «точек» абсолютно безопасно, а когда может привести к фатальному падению производительности системы.

Постановка задачи и исходный запрос

Рассмотрим классическую задачу: нам необходимо выбрать данные из табличной части документа и одновременно получить несколько реквизитов из шапки этого же документа (например, КФО, Контрагент, Договор, Комментарий). Автор темы предложил следующий вариант запроса:


ВЫБРАТЬ
    ПередачаОбъектовОСОсновныеСредства.ОсновноеСредство КАК ОсновноеСредство,
    ПередачаОбъектовОСОсновныеСредства.КПС КАК КПС,
    ПередачаОбъектовОСОсновныеСредства.Сумма КАК Сумма,
    ПередачаОбъектовОСОсновныеСредства.Ссылка.КФО КАК КФО,
    ПередачаОбъектовОСОсновныеСредства.Ссылка.Контрагент КАК Контрагент,
    ПередачаОбъектовОСОсновныеСредства.Ссылка.Договор КАК Договор,
    ПередачаОбъектовОСОсновныеСредства.Ссылка.Комментарий КАК Комментарий
ИЗ
    Документ.ПередачаОбъектовОС.ОсновныеСредства КАК ПередачаОбъектовОСОсновныеСредства
ГДЕ
    ПередачаОбъектовОСОсновныеСредства.Ссылка.ВидОперации В(&ВидыПередачи)
    И ПередачаОбъектовОСОсновныеСредства.Ссылка.Дата МЕЖДУ &Дата1 И &Дата2

В данном запросе получение реквизитов шапки (таких как КФО, Контрагент, Договор) происходит через обращение к предопределенному полю Ссылка табличной части. Возникает закономерный вопрос: насколько эффективен такой подход с точки зрения СУБД? Не будет ли лучше заменить эти обращения явным соединением табличной части с шапкой документа?

Как платформа 1С транслирует «точки» в SQL-запрос

Давайте разберем по шагам, что происходит под капотом платформы 1С:Предприятие, когда она парсит запрос с обращениями через точку к реквизитам одного и того же объекта. Некоторые начинающие разработчики полагают, что каждое обращение вида Ссылка.Реквизит порождает отдельное соединение с таблицей СУБД. Это заблуждение.

Технологическая платформа 1С оптимизирует трансляцию запросов. Если в рамках одного запроса (в полях выборки или условиях фильтрации) происходит обращение к разным реквизитам одного и того же ссылочного объекта, платформа группирует эти обращения. На уровне СУБД будет сгенерировано ровно одно левое соединение (LEFT OUTER JOIN) с таблицей этого объекта.

Таким образом, исходный запрос автора темы будет транслирован в SQL-запрос, где табличная часть _DocumentXX_VT_YY соединяется с таблицей шапки _DocumentXX по полю _DescriptionRef (или аналогичному внутреннему идентификатору ссылки).

Альтернативный вариант с явным левым соединением

Для сравнения рассмотрим, как выглядел бы этот же запрос, если бы мы написали его с использованием явного левого соединения, как часто рекомендуют на различных курсах по оптимизации:


ВЫБРАТЬ
    ПередачаОбъектовОСОсновныеСредства.ОсновноеСредство КАК ОсновноеСредство,
    ПередачаОбъектовОСОсновныеСредства.КПС КАК КПС,
    ПередачаОбъектовОСОсновныеСредства.Сумма КАК Сумма,
    ДокументШапка.КФО КАК КФО,
    ДокументШапка.Контрагент КАК Контрагент,
    ДокументШапка.Договор КАК Договор,
    ДокументШапка.Комментарий КАК Комментарий
ИЗ
    Документ.ПередачаОбъектовОС.ОсновныеСредства КАК ПередачаОбъектовОСОсновныеСредства
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПередачаОбъектовОС КАК ДокументШапка
        ПО ПередачаОбъектовОСОсновныеСредства.Ссылка = ДокументШапка.Ссылка
ГДЕ
    ДокументШапка.ВидОперации В(&ВидыПередачи)
    И ДокументШапка.Дата МЕЖДУ &Дата1 И &Дата2

На первый взгляд, этот запрос кажется более контролируемым. Мы явно указываем СУБД, какую таблицу и по какому условию необходимо присоединить. Но давайте обратимся к практике и посмотрим, как эти два варианта ведут себя на реальной базе данных под управлением MS SQL Server.

Анализ планов выполнения запросов в MS SQL Server

В ходе обсуждения на форуме был проведен наглядный эксперимент на крупной базе данных (выборка за год, объем возвращаемых данных — более 2.5 миллионов строк). Были замерены показатели производительности и проанализированы планы выполнения (Execution Plans) для обоих вариантов написания запроса.

Результаты первоначального замера (на тестовой копии базы):

  1. Вариант с явным соединением: Время выполнения составило в среднем 4630 мс, количество чтений (logical reads) — 1 609 052. В плане выполнения наблюдался оператор Index Seek для поиска по дате.
  2. Вариант с разыменованием через точку: Время выполнения составило в среднем 4250 мс, количество чтений — 1 067 237. В плане выполнения неожиданно использовался оператор Clustered Index Scan (сканирование кластерного индекса), что обычно считается менее эффективным, но в данном конкретном случае за счет кэширования и особенностей распределения данных запрос отработал даже быстрее.

Такая разница в планах выполнения при одинаковой логической сути запросов вызвала вопросы. Почему СУБД построила разные планы для фактически идентичных по структуре запросов?

Влияние актуальности статистики СУБД на выбор плана

Разгадка кроется в состоянии базы данных, на которой проводился первоначальный тест. На тестовой копии статистика распределения данных СУБД оказалась устаревшей. Как только на тестовой базе была выполнена команда обновления статистики (UPDATE STATISTICS), оптимизатор MS SQL Server перестроил планы выполнения.

Результаты после обновления статистики:

После актуализации статистических данных СУБД стала абсолютно одинаково оценивать стоимость выполнения обоих запросов. План выполнения для запроса через точку стал стопроцентно идентичен плану выполнения запроса через явное левое соединение. Время выполнения и количество логических чтений полностью сравнялись.

Это подтверждает важнейший теоретический аспект: современный оптимизатор СУБД (будь то MS SQL Server или PostgreSQL) при наличии актуальной статистики отлично понимает структуру связей «табличная часть — шапка документа». Физическая связь всегда идет по уникальному первичному ключу Ссылка (который гарантированно индексирован), поэтому СУБД оптимизирует неявный LEFT JOIN (порожденный точками) точно так же, как и явный.

Когда «точки» действительно опасны: Составные типы данных

Из результатов эксперимента может показаться, что разницы между точками и соединениями нет вообще, и можно всегда писать запросы через точку ради экономии времени и улучшения читаемости кода. Однако это справедливо только для простых (однотипных) полей.

Давайте выясним причину, почему разыменование через точку считается одной из главных угроз производительности в 1С. Это происходит, когда поле, к которому мы обращаемся через точку, имеет составной тип данных.

Представим, что мы пишем запрос к таблице регистра накопления и хотим получить дату регистратора. Поле Регистратор по умолчанию имеет составной тип, в который могут входить десятки различных документов. Если мы напишем:


ВЫБРАТЬ
    Продажи.Регистратор.Дата КАК ДатаРегистратора
ИЗ
    РегистрНакопления.Продажи КАК Продажи

Платформа 1С будет вынуждена построить в результирующем SQL-запросе левое соединение (LEFT JOIN) со всеми таблицами документов, которые могут выступать в роли регистратора для данного регистра! Если в состав типа входит 50 документов, мы получим гигантский SQL-запрос с 50 соединениями. Оптимизатор СУБД гарантированно запутается в таком запросе, построит неоптимальный план выполнения, что приведет к колоссальным блокировкам и зависанию системы.

Как решать проблему составных типов:

Если вам действительно необходимо получить реквизит составного поля через точку, вы обязаны предварительно ограничить тип этого поля с помощью конструкции ВЫРАЗИТЬ(). Рассмотрим правильный пример:


ВЫБРАТЬ
    ВЫРАЗИТЬ(Продажи.Регистратор КАК Документ.РеализацияТоваровУслуг).Дата КАК ДатаРегистратора
ИЗ
    РегистрНакопления.Продажи КАК Продажи

В этом случае платформа 1С сделает левое соединение только с одной таблицей документа РеализацияТоваровУслуг, и запрос отработает моментально.

Влияние механизмов RLS на неявные соединения

Еще один важный фактор, который необходимо учитывать в реальных корпоративных системах — это механизм ограничения доступа на уровне записей (RLS). Если вы обращаетесь через точку к реквизиту объекта (например, Ссылка.Контрагент.Наименование), на который наложены правила RLS, платформа 1С автоматически добавит в итоговый SQL-текст все подзапросы, проверяющие права текущего пользователя на таблицу контрагентов.

Если в одном запросе присутствует множество таких переходов по точкам к разным справочникам с активным RLS, итоговый текст SQL-запроса раздуется до нечитаемых СУБД масштабов. В таких сложных сценариях явное левое соединение позволяет разработчику более жестко контролировать выборку и минимизировать негативное влияние RLS.

Официальные стандарты разработки фирмы «1С»

Обратимся к рекомендациям ИТС. Стандарты разработки регламентируют следующие правила:

Резюме и практические выводы

Проанализировав ситуацию, мы можем подвести итоги совместной работы по исследованию производительности запросов:

  1. Для простых связей (выборка реквизитов шапки документа из его же табличной части) разыменование через точку **абсолютно эквивалентно** явному левому соединению по производительности. Платформа 1С генерирует одинаковый SQL-код, а СУБД строит идентичные планы выполнения.
  2. Замена точек на явные LEFT JOIN в простых запросах — это «оптимизация ради оптимизации», которая лишь раздувает текст запроса и ухудшает его читаемость для других разработчиков.
  3. Ключевым фактором производительности СУБД является **актуальность статистики**. Если статистика базы данных устарела, СУБД может построить неоптимальный план как для запроса с точками, так и для запроса с соединениями. Не забывайте регулярно выполнять регламентные процедуры обслуживания баз данных.
  4. При работе с полями составного типа (Регистратор, Владелец, составные свойства) использование разыменования через точку без явного ограничения типа через ВЫРАЗИТЬ() **категорически запрещено**.
← На главную