При разработке высоконагруженных систем на платформе 1С:Предприятие 8 программисты часто сталкиваются с ситуациями, когда на первый взгляд «легкий» запрос начинает выполняться неоправданно долго. Одной из неочевидных причин просадки производительности является механизм формирования строкового представления объектов в языке запросов. В этой статье мы разберем, почему стандартная функция ПРЕДСТАВЛЕНИЕ() может порождать лишние соединения (LEFT JOIN) в SQL-запросе и как это связано с переопределением логики в модуле менеджера объекта.
Рассмотрим стандартную ситуацию. У нас есть справочник Справочник1, в котором переопределена процедура ОбработкаПолученияПолейПредставления. Это делается для того, чтобы вместо стандартного наименования или кода платформа выводила в качестве представления значение определенного реквизита (или комбинацию полей).
Пример кода в модуле менеджера справочника:
Процедура ОбработкаПолученияПолейПредставления(Поля, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
Поля.Добавить("Реквизит1");
КонецПроцедуры
Теперь проанализируем, как ведут себя разные варианты написания запроса для получения представления этого справочника. Посмотрим на два варианта, которые, казалось бы, должны работать идентично.
Вариант 1: Обращение через стандартное поле Представление
ВЫБРАТЬ
Справочник1.Представление КАК Представление
ИЗ
Справочник.Справочник1 КАК Справочник1
При выполнении этого запроса SQL-профайлер покажет нам оптимальную и понятную картинку. Платформа просто выбирает нужное поле из таблицы:
SELECT T1._Fld72 -- (где _Fld72 это наш Реквизит1)
FROM dbo._Reference71 T1
Вариант 2: Использование функции ПРЕДСТАВЛЕНИЕ(Ссылка)
ВЫБРАТЬ
ПРЕДСТАВЛЕНИЕ(Справочник1.Ссылка) КАК Представление
ИЗ
Справочник.Справочник1 КАК Справочник1
Здесь нас ждет сюрприз. Несмотря на то, что мы запрашиваем данные из той же самой таблицы, 1С транслирует запрос следующим образом:
SELECT T2._Fld72
FROM dbo._Reference71 T1
LEFT OUTER JOIN dbo._Reference71 T2
ON T1._IDRRef = T2._IDRRef
Мы видим лишнее левое соединение таблицы самой с собой по первичному ключу. На справочнике в 10 000 элементов это не будет заметно, но если в таблице миллионы записей, такое соединение может значительно увеличить нагрузку на CPU и количество логических чтений в СУБД.
Выясним причину такого поведения генератора запросов. Когда в объекте переопределена процедура ОбработкаПолученияПолейПредставления, платформа помечает этот тип данных как имеющий «сложное» представление. Логика работы функции ПРЕДСТАВЛЕНИЕ() в запросе универсальна:
ПРЕДСТАВЛЕНИЕ() предназначена для обработки в том числе составных типов данных. Если бы поле имело составной тип, 1С была бы обязана построить LEFT JOIN ко всем возможным таблицам, входящим в этот тип.FROM. Он видит задачу: «получить представление для ссылки типа Справочник1» и отрабатывает её по стандартному шаблону — через соединение с таблицей-источником.Проанализируем ситуацию с точки зрения СУБД. Несмотря на то, что соединение идет по первичному ключу (_IDRRef), который всегда проиндексирован, лишняя операция в плане запроса не проходит бесследно. Обычно в плане мы увидим Merge Join или Hash Match и два сканирования индекса (Index Scan).
Основные риски:
ПРЕДСТАВЛЕНИЕ() для всех ссылочных полей. В итоге итоговый SQL-запрос превращается в «простыню» из десятков LEFT JOIN, что может привести к падению производительности отчета в разы.ПРЕДСТАВЛЕНИЕ(), полученные через запрос, не кешируются платформой так же эффективно, как представления, полученные через объектную технику (Ссылка.Метаданные().Представление()).Разберем по шагам, как избежать деградации производительности при работе с представлениями в больших таблицах.
Справочник.Реквизит1. Это исключит работу механизмов формирования представления на уровне СУБД.Справочник1.Представление зачастую транслируется более оптимально, чем ПРЕДСТАВЛЕНИЕ(Справочник1.Ссылка), так как платформа сразу видит метаданные текущей таблицы и может избежать лишнего соединения.ОбработкаПолученияПолейПредставления «лишний» join по-прежнему остается стабильным поведением платформы.Резюме: Знание того, как 1С преобразует высокоуровневые функции в SQL-код, позволяет избегать «детских» ошибок на больших объемах данных. Помните, что универсальность функции ПРЕДСТАВЛЕНИЕ() — это её слабое место с точки зрения производительности.