В практике разработчика 1С часто встречается ситуация, когда при анализе работы системы через SQL Profiler обнаруживается странное поведение: текст запроса, сформированный платформой, выглядит громоздким и содержит десятки LEFT OUTER JOIN, в то время как графический план выполнения в SQL Server Management Studio (SSMS) показывает работу всего с парой таблиц. Разберем подробно, почему возникает этот диссонанс, кому стоит верить и как сделать код более эффективным.
Рассмотрим ситуацию на конкретном примере. Допустим, мы пишем простой запрос к регистру накопления, чтобы получить дату регистратора:
ВЫБРАТЬ ПЕРВЫЕ 10
ЗаказыКлиентов.Регистратор.Дата КАК РегистраторДата
ИЗ
РегистрНакопления.ЗаказыКлиентов КАК ЗаказыКлиентов
ГДЕ
ЗаказыКлиентов.Регистратор ССЫЛКА Документ.ЗаказКлиента
Проанализируем, что происходит "под капотом". Поле Регистратор в любом регистре является составным типом данных. На уровне СУБД это означает, что ссылка хранится в нескольких колонках (тип, идентификатор). Когда мы обращаемся "через точку" к реквизиту Дата, платформа 1С не знает заранее, какой именно тип документа там окажется в момент выполнения, поэтому она генерирует SQL-код, включающий соединения со всеми возможными таблицами документов, которые могут быть регистраторами для этого регистра.
В тексте запроса (SQL Profiler) мы увидим огромный блок CASE и длинный список LEFT OUTER JOIN ко всем таблицам _DocumentX. Однако в секции WHERE платформа добавит условие фильтрации по типу, например: WHERE T1._RecorderTRef = 0x000000D1.
Выясним причину, по которой СУБД игнорирует лишние соединения в плане выполнения. Современные версии MS SQL Server обладают продвинутым оптимизатором, который использует механизм Join Elimination (исключение соединений) и Contradiction Detection (обнаружение противоречий).
Проанализируем логику оптимизатора:
WHERE, которое жестко ограничивает тип регистратора одним конкретным значением.ON в секциях LEFT JOIN.CASE) при невыполнении условия все равно превратятся в NULL, СУБД принимает решение вообще не обращаться к этим таблицам физически.Таким образом, отвечая на вопрос "кому верить", мы можем утверждать: верить нужно плану выполнения. Именно он показывает реальные физические операции (чтения, сканирования индексов), которые выполняет сервер. Текст запроса — это лишь декларация намерений, которую СУБД перерабатывает в оптимальный алгоритм.
Несмотря на "умное" поведение SQL-сервера, оставлять запрос в исходном виде — плохая практика. Рассмотрим, почему стоит использовать ВЫРАЗИТЬ(). Когда мы пишем:
ВЫБРАТЬ ПЕРВЫЕ 10
(ВЫРАЗИТЬ(ЗаказыКлиентов.Регистратор КАК Документ.ЗаказКлиента)).Дата КАК РегистраторДата
ИЗ
РегистрНакопления.ЗаказыКлиентов КАК ЗаказыКлиентов
Мы даем платформе 1С прямую команду: "Я точно знаю, что здесь будет этот тип документа". В этом случае 1С при трансляции в SQL выбросит все лишние LEFT JOIN еще на этапе формирования текста. Это дает несколько преимуществ:
ВЫРАЗИТЬ() гарантирует предсказуемость.Рассмотрим еще один способ, который часто оказывается эффективнее обращения через точку. Мы можем использовать ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN) с конкретной таблицей документа:
ВЫБРАТЬ ПЕРВЫЕ 10
ДокЗаказ.Дата КАК РегистраторДата
ИЗ
РегистрНакопления.ЗаказыКлиентов КАК ЗаказыКлиентов
ВНУТРЕННЕЕ СОЕДИНЕНИЕ
Документ.ЗаказКлиента КАК ДокЗаказ
ПО
ЗаказыКлиентов.Регистратор = ДокЗаказ.Ссылка
В этом случае мы не только избавляемся от лишних соединений, но и помогаем оптимизатору использовать более быстрые алгоритмы соединения, такие как Hash Join или Merge Join, если объем данных велик. Кроме того, INNER JOIN автоматически выполняет роль фильтра, заменяя конструкцию ССЫЛКА в условии ГДЕ.
В современных типовых конфигурациях (ERP 2.5, КА 2.5, БП 3.0) реализован еще более продвинутый механизм получения базовых реквизитов (Дата, Номер). Проанализируем работу регистра сведения ДанныеПервичныхДокументов.
В этот регистр при проведении любого документа записываются его основные данные. Вместо того чтобы соединяться с тяжелыми таблицами документов (где может быть сотни полей), мы можем выполнить одно соединение с легким регистром сведений. Это особенно актуально для отчетов, выводящих реестры документов разных типов.
Подведем итог нашему разбору. Различие между текстом и планом — это нормальная работа оптимизатора СУБД, но это не повод писать неоптимальные запросы на языке 1С.
Основные правила для разработчика:
Регистратор, Владелец, любой другой составной реквизит).ВЫРАЗИТЬ(), если вам нужно получить реквизит конкретного типа из составного поля.ВНУТРЕННЕМУ СОЕДИНЕНИЮ вместо разыменования ссылки.ССЫЛКА в блоке ГДЕ только фильтрует результат, но не сокращает количество соединений в блоке ВЫБРАТЬ, в то время как ВЫРАЗИТЬ влияет непосредственно на структуру соединений.Применяя эти методы, мы делаем систему более устойчивой к росту объема данных и снижаем вероятность внезапной деградации производительности при обновлении платформы или версии СУБД.