При разработке на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с проблемой низкой производительности сложных запросов. Одной из самых распространенных причин замедления работы базы данных является использование вложенных запросов в секции ГДЕ или в качестве источников данных в секции ИЗ. В этой статье мы подробно разберем, почему вложенные запросы могут «тормозить» систему и как правильно переписать их с использованием временных таблиц для достижения максимальной скорости выполнения.
Для начала проанализируем ситуацию с точки зрения системы управления базами данных (СУБД). Когда мы используем вложенный запрос, СУБД (например, MS SQL Server или PostgreSQL) не всегда может эффективно построить план выполнения. Вложенный запрос зачастую воспринимается оптимизатором как виртуальная таблица, для которой невозможно заранее рассчитать статистику или использовать индексы внешних таблиц.
Рассмотрим основные недостатки вложенных запросов:
Для оптимизации мы будем использовать механизм временных таблиц. Рассмотрим подробнее, как это работает. Временная таблица — это физическая таблица в базе данных (в системной базе tempdb для SQL Server), которая создается в процессе выполнения запроса. Мы можем поместить в нее промежуточный результат, проиндексировать его и затем использовать в основном запросе.
Разберем по шагам процесс трансформации кода. Допустим, у нас есть типичный неоптимальный запрос, который выбирает продажи только по тем контрагентам, у которых есть определенное свойство.
Пример неоптимального кода с вложенным запросом:
ВЫБРАТЬ
ПродажиОбороты.Контрагент,
ПродажиОбороты.СуммаОборот
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода) КАК ПродажиОбороты
ГДЕ
ПродажиОбороты.Контрагент В
(ВЫБРАТЬ
СвойстваКонтрагентов.Ссылка
ИЗ
Справочник.Контрагенты КАК СвойстваКонтрагентов
ГДЕ
СвойстваКонтрагентов.ВажныйКлиент = ИСТИНА)
Такой запрос может работать медленно, если в справочнике Контрагенты тысячи записей. Проанализируем, как сделать его лучше.
Вместо использования конструкции В (ВЫБРАТЬ...), мы сначала выберем список нужных нам контрагентов в отдельную временную таблицу. Для этого используем ключевое слово ПОМЕСТИТЬ.
Посмотрим на пример кода:
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент
ПОМЕСТИТЬ ВТ_ВажныеКонтрагенты
ИЗ
Справочник.Контрагенты КАК Контрагенты
ГДЕ
Контрагенты.ВажныйКлиент = ИСТИНА
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ПродажиОбороты.Контрагент,
ПродажиОбороты.СуммаОборот
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода) КАК ПродажиОбороты
ГДЕ
ПродажиОбороты.Контрагент В (ВЫБРАТЬ ВТ.Контрагент ИЗ ВТ_ВажныеКонтрагенты КАК ВТ)
Просто поместить данные во временную таблицу недостаточно для полной оптимизации. Важным моментом является использование индексации. Если во временной таблице более 20-50 записей, индекс обязателен. Это позволит СУБД мгновенно находить соответствия при соединении таблиц.
Добавим индексацию в наш пример:
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент
ПОМЕСТИТЬ ВТ_ВажныеКонтрагенты
ИЗ
Справочник.Контрагенты КАК Контрагенты
ГДЕ
Контрагенты.ВажныйКлиент = ИСТИНА
ИНДЕКСИРОВАТЬ ПО
Контрагент
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ПродажиОбороты.Контрагент,
ПродажиОбороты.СуммаОборот
ИЗ
ВТ_ВажныеКонтрагенты КАК ВТ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода) КАК ПродажиОбороты
ПО ВТ.Контрагент = ПродажиОбороты.Контрагент
Обратите внимание: мы заменили условие ГДЕ... В (...) на ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Это наиболее эффективный способ фильтрации данных в 1С, так как он позволяет оптимизатору СУБД использовать наиболее быстрые алгоритмы соединения (Nested Loops или Merge Join).
В программном коде 1С для работы с временными таблицами часто используется объект МенеджерВременныхТаблиц. Он позволяет передавать созданные таблицы между разными объектами Запрос. Это полезно, когда логика подготовки данных сложна и разбита на несколько этапов.
Рассмотрим пример на языке 1С:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Товар
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.ВидНоменклатуры = &Вид";
Запрос.УстановитьПараметр("Вид", Справочники.ВидыНоменклатуры.Товар);
Запрос.Выполнить();
// Теперь временная таблица ВТ_Товары живет в объекте МенеджерВТ
// Мы можем использовать её в следующем запросе
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Товар,
| Остатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Товар В (ВЫБРАТЬ Т.Товар ИЗ ВТ_Товары КАК Т)) КАК Остатки";
Результат = Запрос.Выполнить().Выгрузить();
Несмотря на общие рекомендации, существуют исключения. Рассмотрим ситуации, когда вложенный запрос допустим:
ВЫБРАТЬ (скалярные запросы) иногда неизбежны, но их стоит минимизировать.Однако в 90% случаев при разработке в 1С:ERP, 1С:УТ или 1С:Бухгалтерия, использование временных таблиц является стандартом качественного кода.
Мы проанализировали подходы к оптимизации и выяснили, что временные таблицы — это мощный инструмент в руках разработчика 1С. Они позволяют:
;, гораздо легче читать и отлаживать.Используйте метод ИНДЕКСИРОВАТЬ ПО всегда, когда во временной таблице есть поля, по которым в дальнейшем будет происходить соединение или фильтрация. Помните, что производительность вашего решения напрямую зависит от качества написанных SQL-запросов, которые генерирует платформа 1С.