При разработке на платформе 1С:Предприятие программисты часто сталкиваются с ситуацией, когда простой и логичный запрос начинает выполняться непозволительно долго. Особенно остро эта проблема проявляется в файловом варианте работы базы данных. Рассмотрим классический случай: выборка данных из табличной части документа с получением дополнительных реквизитов через ссылку (разыменование). Если без обращения к реквизитам справочника запрос «летает» (0.5 сек), а с ними — «зависает» (5-7 сек), значит, мы столкнулись с особенностями работы оптимизатора запросов и механизмом LEFT JOIN.
В этой статье мы разберем по шагам, почему так происходит, проанализируем поведение системы и научимся писать оптимальные запросы, которые работают быстро даже на больших объемах данных в файловом режиме.
Когда мы пишем в тексте запроса конструкцию вида расходнаяНакладнаятовары.товар.полнНаименование, платформа 1С автоматически преобразует это в левое соединение (LEFT OUTER JOIN) на уровне SQL-запроса. Если таких обращений несколько (например, к наименованию, производителю и импортеру), количество соединений увеличивается.
Проанализируем ситуацию: в файловой базе данных (движок 1CD) оптимизатор запросов значительно проще, чем в промышленных СУБД вроде MS SQL Server или PostgreSQL. Основная проблема заключается в том, что при наличии соединений и условий в секции ГДЕ, файловая СУБД часто сначала пытается выполнить соединение всех строк таблиц, а уже потом накладывает фильтр. Если в табличной части документов сотни тысяч строк, а в справочнике товаров — десятки тысяч, система пытается построить в памяти гигантскую временную таблицу, что и вызывает тормоза.
Ситуация усугубляется, если условие фильтрации также идет «через точку», например: ГДЕ расходнаяНакладнаятовары.Ссылка.WBRegId = &WBRegId. В этом случае для каждой строки табличной части система вынуждена обращаться к основной таблице документа, чтобы проверить значение реквизита шапки.
Наиболее эффективный способ ускорить запрос в 1С — это принудительная фильтрация данных с использованием временных таблиц. Мы разделяем задачу на два этапа: сначала выбираем только нужные строки документов во временную таблицу, а затем соединяем эту «маленькую» таблицу с тяжелыми справочниками.
Рассмотрим пример оптимизированного запроса:
ВЫБРАТЬ
расходнаяНакладнаятовары.Ссылка КАК Ссылка,
расходнаяНакладнаятовары.НомерСтроки КАК НомерСтроки,
расходнаяНакладнаятовары.товар КАК товар,
...
расходнаяНакладнаятовары.Ссылка.WBRegId КАК WBRegId
ПОМЕСТИТЬ тбл_рез
ИЗ
Документ.расходнаяНакладная.товары КАК расходнаяНакладнаятовары
ГДЕ
расходнаяНакладнаятовары.Ссылка.WBRegId = &WBRegId
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
тбл_рез.Ссылка,
тбл_рез.НомерСтроки,
тбл_рез.товар,
номенклатура.полнНаименование КАК товарполнНаименование,
номенклатура.производитель КАК товарпроизводитель
ИЗ
тбл_рез КАК тбл_рез
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК номенклатура
ПО тбл_рез.товар = номенклатура.Ссылка
Почему это работает быстрее? В первом пакете запроса система находит только те строки, которые относятся к конкретному документу (или группе документов). Во временную таблицу тбл_рез попадает, скажем, всего 50 строк. Во втором пакете соединение происходит уже не между миллионами записей, а между 50 строками и справочником. Это выполняется мгновенно.
Как мы заметили ранее, условие расходнаяНакладнаятовары.Ссылка.WBRegId = &WBRegId заставляет систему «прыгать» из таблицы строк в таблицу шапок документов. В файловом режиме это очень дорогая операция. Попробуем оптимизировать еще сильнее, сначала отобрав саму ссылку на документ.
Разберем этот подход:
ВЫБРАТЬ
Док.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_Документы
ИЗ
Документ.расходнаяНакладная КАК Док
ГДЕ
Док.WBRegId = &WBRegId
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ТЧ.Ссылка,
ТЧ.товар,
ТЧ.товар.полнНаименование
ИЗ
Документ.расходнаяНакладная.товары КАК ТЧ
ГДЕ
ТЧ.Ссылка В (ВЫБРАТЬ Ссылка ИЗ ВТ_Документы)
В этом случае система сначала находит документ по индексу реквизита WBRegId в маленькой таблице шапок, а затем делает выборку из строк только по конкретному ID документа. Это исключает лишние соединения на этапе первичного отбора.
Если реквизит товар имеет составной тип (например, Справочник.Номенклатура и Справочник.Услуги), то обращение через точку товар.полнНаименование заставит 1С сделать левое соединение со всеми таблицами, входящими в составной тип. Это критически замедляет запрос.
В таких случаях необходимо явно ограничивать тип с помощью функции ВЫРАЗИТЬ. Рассмотрим на примере:
ВЫРАЗИТЬ(расходнаяНакладнаятовары.товар КАК Справочник.Номенклатура).полнНаименование
Использование ВЫРАЗИТЬ подсказывает системе, что соединение нужно строить только с таблицей справочника Номенклатура, игнорируя остальные возможные типы. Это значительно упрощает план запроса.
Для того чтобы условие в ГДЕ работало быстро, поле, по которому идет отбор, должно быть проиндексировано. В нашем примере это реквизит WBRegId.
Важные моменты индексации:
WBRegId в метаданных установлено свойство Индексировать.Товар в ТЧ (хотя обычно 1С индексирует его автоматически при создании связи).Оптимизация запросов в 1С, особенно для файловых баз — это искусство управления соединениями. Проанализировав ситуацию, мы пришли к выводу, что «красивый» код с кучей точек в запросе часто превращается в «медленный» SQL-код.
Золотые правила оптимизации:
ВЫРАЗИТЬ.ЛЕВОЕ СОЕДИНЕНИЕ в тексте запроса часто работает предсказуемее, чем автоматическое разыменование ссылки через точку.Следуя этим рекомендациям, вы сможете добиться ускорения запросов в десятки раз, превратив пятисекундное ожидание в мгновенный отклик системы.