Как оптимально написать запрос с соединением по составному типу в 1С?

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

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

Проанализируем ситуацию и исходный запрос

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

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


ВЫБРАТЬ 
    усЕдиницыИзмерения.Ссылка КАК Ссылка, 
    ск_ИдентификаторыОбменаВнешнегоИсточника.US_OID КАК US_OID
ИЗ 
    Справочник.усЕдиницыИзмерения КАК усЕдиницыИзмерения
ВНУТРЕННЕЕ СОЕДИНЕНИЕ 
    РегистрСведений.ск_ИдентификаторыОбменаВнешнегоИсточника КАК ск_ИдентификаторыОбменаВнешнегоИсточника
ПО 
    (ск_ИдентификаторыОбменаВнешнегоИсточника.Объект.Ссылка = усЕдиницыИзмерения.Ссылка)

В этом запросе кроется серьезная архитектурная ошибка, из-за которой СУБД (например, MS SQL Server) строит неэффективный план выполнения с огромным количеством так называемых "лесенок".

Посмотрим, как физически хранятся составные типы

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

  1. Колонку для хранения типа значения (часто имеет суффикс _TYPE).
  2. Колонку для хранения уникального идентификатора таблицы (суффикс _RTRef).
  3. Колонку для хранения самого идентификатора записи — ссылки (суффикс _RRRef).

Именно поэтому любое соединение по составному типу всегда обходится планировщику запросов дороже: ему приходится сопоставлять сразу несколько колонок.

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

Главная проблема медленного запроса кроется в условии соединения: ск_ИдентификаторыОбменаВнешнегоИсточника.Объект.Ссылка = усЕдиницыИзмерения.Ссылка.

Любое обращение "через точку" к полю составного типа (в нашем случае Объект.Ссылка) заставляет платформу 1С выполнять разыменование ссылочного поля. Так как тип поля Объект является составным и содержит более 20 различных видов ссылок, 1С не знает заранее, к какой именно таблице вы обращаетесь. Поэтому платформа "молча" генерирует подзапросы и неявные левые соединения (LEFT JOIN) ко всем возможным 20+ таблицам, входящим в этот составной тип.

Это приводит к формированию "монструозного" SQL-запроса, который СУБД выполняет очень долго.

Разберем правильное решение: прямое сравнение

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


ВЫБРАТЬ 
    втЗаказы.Ссылка КАК Ссылка, 
    ИдентификаторыОбмена.OID КАК OID
ИЗ 
    втЗаказы КАК втЗаказы
ЛЕВОЕ СОЕДИНЕНИЕ 
    РегистрСведений.ск_ИдентификаторыОбменаВнешнегоИсточника КАК ИдентификаторыОбмена
ПО 
    втЗаказы.Ссылка = ИдентификаторыОбмена.Объект

В этом случае платформа 1С видит, что слева находится строго типизированная таблица (втЗаказы.Ссылка). При трансляции запроса в SQL она сама автоматически и оптимально добавит условия равенства по полям _TYPE, _RTRef и _RRRef, не создавая ни одного лишнего левого соединения.

Функция ВЫРАЗИТЬ и оператор ССЫЛКА: когда они нужны?

В процессе поиска оптимального решения разработчики часто пытаются использовать оператор ССЫЛКА или функцию ВЫРАЗИТЬ. Разберем их различия и влияние на запрос.

Оператор ССЫЛКА: Конструкция вида ГДЕ Объект ССЫЛКА Документ.УсЗаказНаОтгрузку транслируется в SQL как обычное условие секции WHERE на проверку колонки типа (_RTRef). Это отлично фильтрует строки, но если в секции ВЫБРАТЬ или ПО вы уже использовали обращение "через точку" (Объект.Ссылка), оператор ССЫЛКА не предотвратит создание неявных левых соединений! Тормоза останутся.

Функция ВЫРАЗИТЬ: Конструкция ВЫРАЗИТЬ(Объект КАК Документ.УсЗаказНаОтгрузку) работает принципиально иначе. Она действует на этапе построения текста SQL-запроса самой платформой. Она жестко "отсекает" все остальные типы, кроме указанного. Если после применения ВЫРАЗИТЬ вам все-таки нужно получить реквизит через точку (например, номер или дату документа), 1С поймет, что нужна только одна таблица из 20, и физически присоединит только её.

Особенности индексирования составных типов

Если соединение по-прежнему работает недостаточно быстро, можно рассмотреть установку свойства Индексировать для измерения регистра. Однако делать это нужно с осторожностью.

При создании индекса по составному полю 1С формирует ключи с учетом всех суффиксов. Если в регистре миллионы записей и множество типов, такой индекс значительно увеличит объем базы данных на жестком диске. Да, чтение и поиск ускорятся (вместо Index Scan мы получим Index Seek), но любое изменение данных регистра (запись, обновление, удаление) начнет работать медленнее из-за необходимости перестроения большого объема индексных структур. Индексировать поля составного типа рекомендуется только при острой необходимости.

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

  1. Категорически избегайте разыменования составных полей. Никогда не используйте конструкции вроде СоставноеПоле.Ссылка = НашаСсылка. Всегда соединяйте напрямую: СоставноеПоле = НашаСсылка.
  2. Если вам необходимо получить реквизит составного поля в запросе, обязательно типизируйте его с помощью функции ВЫРАЗИТЬ перед тем, как обращаться к полям через точку.
  3. В сложных случаях с "тяжелыми" соединениями рассмотрите вариант первоначальной фильтрации нужного типа во временную таблицу (с наложением условия ССЫЛКА), ее индексирование, и последующее соединение с основной таблицей.
  4. Старайтесь на уровне архитектуры избегать использования типов "Любая ссылка" (СправочникСсылка, ДокументСсылка без явного указания объектов), если заранее известны конкретные виды объектов, которые будут там храниться.
← На главную