В практике разработки на платформе 1С:Предприятие мы часто сталкиваемся с ситуацией, когда, казалось бы, простой запрос выполняется неоправданно долго. В таких случаях разработчику необходимо провести замер производительности контура 1С и поиск узких мест, чтобы понять причину деградации системы. Одной из самых распространенных причин падения производительности является использование так называемых "неявных соединений" или, проще говоря, обращение к реквизитам ссылочных типов через точку.
В этой статье мы подробно разберем, почему удобный синтаксис Документ.Контрагент.Менеджер.ФизЛицо может "убить" производительность вашей базы данных, проанализируем, как платформа преобразует такие конструкции в SQL-запросы, и научимся писать правильный, оптимизированный код. Мы рассмотрим эту проблему комплексно, основываясь на обсуждениях экспертов и документации.
Когда мы пишем код на встроенном языке 1С или составляем текст запроса, платформа предоставляет нам удобный объектный доступ к данным. Например, если нам нужно получить фамилию менеджера, который указан в заказе клиента, рука сама тянется написать:
ВЫБРАТЬ
ЗаказКлиента.Ссылка,
ЗаказКлиента.Контрагент.Менеджер.ФизЛицо.Наименование КАК ФИОМенеджера
ИЗ
Документ.ЗаказКлиента КАК ЗаказКлиента
С точки зрения читаемости кода — это идеально. Мы сразу видим логическую цепочку связей. Однако, с точки зрения СУБД (SQL Server, PostgreSQL), такой конструкции не существует. СУБД понимает только таблицы и соединения между ними. Чтобы лучше понимать эти процессы, полезно анализировать SQL сервер глазами 1С-ника, сопоставляя код 1С с итоговым SQL-запросом.
Давайте проанализируем, что происходит "под капотом", когда транслятор языка запросов 1С превращает этот текст в SQL-инструкцию. Каждая точка в пути к данным преобразуется в отдельное ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN).
В нашем примере цепочка Контрагент.Менеджер.ФизЛицо.Наименование заставит платформу сделать следующее:
Если в выборке участвуют тысячи документов, СУБД вынуждена выполнять каскад тяжелых операций соединения таблиц, даже если многие поля (например, Менеджер) не заполнены. Это создает колоссальную нагрузку на дисковую подсистему и процессор сервера БД.
Давайте рассмотрим конкретный пример из практики, который часто вызывает проблемы. Допустим, нам нужно выбрать номенклатуру из табличной части документа и показать страну производителя этой номенклатуры.
Неоптимизированный запрос выглядит так:
ВЫБРАТЬ
РеализацияТоваровУслугТовары.Номенклатура.Наименование КАК Товар,
РеализацияТоваровУслугТовары.Номенклатура.СтранаПроисхождения.Наименование КАК Страна
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
ГДЕ
РеализацияТоваровУслугТовары.Ссылка = &Ссылка
На первый взгляд, все безобидно. Но давайте посчитаем количество неявных соединений. Здесь мы обращаемся к таблице номенклатуры дважды. В старых версиях платформы это могло приводить к двум отдельным соединениям с одной и той же таблицей. В современных версиях оптимизатор платформы стал умнее и может сворачивать одинаковые пути, но риск остается.
Более опасная ситуация возникает, когда мы используем обращение через точку в секции ГДЕ. Посмотрим на такой код:
ВЫБРАТЬ
Заказ.Ссылка
ИЗ
Документ.ЗаказКлиента КАК Заказ
ГДЕ
Заказ.Контрагент.ОсновнойМенеджер.ФизЛицо.Пол = &Мужской
В данном случае СУБД не может использовать индексы эффективно, так как фильтрация происходит по полю, присоединенному через несколько таблиц (levels of indirection). Это часто приводит к полному сканированию таблиц (Table Scan), что на больших объемах данных вызывает "тормоза".
Чтобы ускорить работу запроса и сделать его предсказуемым для планировщика СУБД, нам необходимо отказаться от использования точек и прописать соединения явно. Это даст нам полный контроль над тем, как и в каком порядке соединяются таблицы. Если вам нужно быстро вспомнить синтаксис, рекомендуем использовать этот шпаргалка по языку запросов.
Давайте перепишем наш первый пример с менеджером, используя явные ЛЕВОЕ СОЕДИНЕНИЕ.
ВЫБРАТЬ
ЗаказКлиента.Ссылка,
ФизическиеЛица.Наименование КАК ФИОМенеджера
ИЗ
Документ.ЗаказКлиента КАК ЗаказКлиента
// Первое соединение: Получаем менеджера контрагента
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК СпрКонтрагенты
ПО ЗаказКлиента.Контрагент = СпрКонтрагенты.Ссылка
// Второе соединение: Получаем физлицо менеджера
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Пользователи КАК СпрПользователи
ПО СпрКонтрагенты.Менеджер = СпрПользователи.Ссылка
// Третье соединение: Получаем имя физлица
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ФизическиеЛица КАК ФизическиеЛица
ПО СпрПользователи.ФизЛицо = ФизическиеЛица.Ссылка
Почему этот код лучше?
ВЫРАЗИТЬ или просто соединяясь с конкретным справочником).ПО ...) позволяют SQL-серверу эффективнее использовать индексы.Особое внимание уделим реквизитам составного типа. Это классическая ловушка для начинающих программистов 1С. Рассмотрим ситуацию с регистром сведений, где измерение Документ может быть ссылкой на ЗаказКлиента, Реализацию или Возврат.
Если мы напишем:
ВЫБРАТЬ Записи.Документ.Дата ...
Платформа создаст SQL-запрос, в котором будет ЛЕВОЕ СОЕДИНЕНИЕ к каждой из трех таблиц документов! Это генерирует огромный и медленный запрос.
Как исправить? Использовать функцию ВЫРАЗИТЬ (CAST) перед обращением к полю.
ВЫБРАТЬ
ВЫРАЗИТЬ(Записи.Документ КАК Документ.ЗаказКлиента).Дата
ИЗ
РегистрСведений.СтатусыДокументов КАК Записи
ГДЕ
Записи.Документ ССЫЛКА Документ.ЗаказКлиента
В этом случае платформа поймет, что нас интересует только таблица ЗаказКлиента, и сделает соединение только с ней. Это критически важно для производительности.
Еще один аспект, который мы должны учитывать — это RLS (Row Level Security). Когда в конфигурации включено ограничение прав доступа на уровне записей, каждое "неявное соединение" через точку усложняет проверку прав.
Представим, что у пользователя ограничен доступ к справочнику Контрагенты. Если мы обращаемся Заказ.Контрагент.Наименование, система должна:
Если цепочка длинная (Заказ.Контрагент.Менеджер.ГруппаДоступа...), то к основному запросу "прицепляются" подзапросы проверки прав для каждой таблицы в цепочке. Это может замедлить выполнение запроса в десятки раз.
При использовании явных соединений мы также не избавляемся от RLS, но структура запроса становится более плоской, и часто оптимизатору СУБД проще построить план выполнения.
Не стоит впадать в крайности и полностью запрещать использование точек. Есть ситуации, когда это оправдано и не несет вреда:
Заказ.Дата или Заказ.Номер не вызывают соединений, так как эти данные лежат в основной таблице документа. Проблема возникает только при обращении к ссылочным полям (переход к другой таблице).Если вы столкнулись с медленным отчетом или обработкой, предлагаем действовать по следующему плану:
Таблица.Реквизит.Поле).ЛЕВОЕ СОЕДИНЕНИЕ (или ВНУТРЕННЕЕ СОЕДИНЕНИЕ, если поле обязательно заполнено).ВЫРАЗИТЬ или фильтр ССЫЛКА.Иногда, даже явные соединения работают медленно, если цепочка слишком длинная. В таких случаях мы рекомендуем использовать временные таблицы.
Стратегия "Разделяй и властвуй":
Стоит отметить, что в новых версиях платформы (8.3.25+) появились дополнительные возможности, такие как добавление данных в существующую временную таблицу, что может быть полезно в сложных алгоритмах.
// Шаг 1: Отбираем заказы во временную таблицу
ВЫБРАТЬ
Заказ.Ссылка КАК Ссылка,
Заказ.Контрагент КАК Контрагент
ПОМЕСТИТЬ ВТ_Заказы
ИЗ
Документ.ЗаказКлиента КАК Заказ
ГДЕ
Заказ.Дата МЕЖДУ &НачалоПериода И &КонецПериода;
ИНДЕКСИРОВАТЬ ПО
Контрагент;
// Шаг 2: Соединяем с контрагентами
ВЫБРАТЬ
ВТ_Заказы.Ссылка,
Контрагенты.Наименование
ИЗ
ВТ_Заказы КАК ВТ_Заказы
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты
ПО ВТ_Заказы.Контрагент = Контрагенты.Ссылка
Такой подход (пакетные запросы) часто является "золотой пулей" для сложных отчетов, позволяя обойти ограничения оптимизатора СУБД и получить результат максимально быстро.
Подведем итог: Неявные соединения (через точку) — это синтаксический сахар, за который приходится платить производительностью. Контролируйте свои запросы, используйте явные JOIN и ВЫРАЗИТЬ для составных типов, и ваши приложения на 1С будут работать быстро и стабильно.