Часто при разработке на платформе 1С мы сталкиваемся с задачей соединения таблиц (документов или справочников) с регистрами сведений, у которых одно из измерений имеет составной тип данных. Если в регистре миллионы записей, а в составном типе десятки возможных объектов, запрос может начать работать катастрофически медленно. Разберем по шагам, почему это происходит, как платформа транслирует такие запросы на уровень СУБД, и выясним причину резкого падения производительности на реальном примере.
Представим задачу: есть таблица заказов (или единиц измерения), которую нужно соединить с большим регистром сведений ИдентификаторыОбменаВнешнегоИсточника (более 2 млн записей). Измерение Объект в регистре имеет составной тип, включающий в себя более 20 различных видов документов и справочников.
Рассмотрим первоначальный вариант запроса, который работал крайне медленно:
ВЫБРАТЬ
усЕдиницыИзмерения.Ссылка КАК Ссылка,
ск_ИдентификаторыОбменаВнешнегоИсточника.US_OID КАК US_OID
ИЗ
Справочник.усЕдиницыИзмерения КАК усЕдиницыИзмерения
ВНУТРЕННЕЕ СОЕДИНЕНИЕ
РегистрСведений.ск_ИдентификаторыОбменаВнешнегоИсточника КАК ск_ИдентификаторыОбменаВнешнегоИсточника
ПО
(ск_ИдентификаторыОбменаВнешнегоИсточника.Объект.Ссылка = усЕдиницыИзмерения.Ссылка)
В этом запросе кроется серьезная архитектурная ошибка, из-за которой СУБД (например, MS SQL Server) строит неэффективный план выполнения с огромным количеством так называемых "лесенок".
Чтобы понять причину, проанализируем, как 1С работает с составными типами на физическом уровне базы данных. Поле составного типа физически не является одной колонкой в таблице СУБД. Платформа неявно создает для него несколько полей:
_TYPE)._RTRef)._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), но любое изменение данных регистра (запись, обновление, удаление) начнет работать медленнее из-за необходимости перестроения большого объема индексных структур. Индексировать поля составного типа рекомендуется только при острой необходимости.
СоставноеПоле.Ссылка = НашаСсылка. Всегда соединяйте напрямую: СоставноеПоле = НашаСсылка.ВЫРАЗИТЬ перед тем, как обращаться к полям через точку.ССЫЛКА), ее индексирование, и последующее соединение с основной таблицей.СправочникСсылка, ДокументСсылка без явного указания объектов), если заранее известны конкретные виды объектов, которые будут там храниться.