В процессе разработки на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с проблемой падения производительности при выполнении сложных запросов. Особенно остро эта проблема проявляется при работе с большими объемами данных, когда в одном запросе используются множественные вложенные подзапросы или сложные соединения. Сегодня мы проанализируем, почему подзапросы могут замедлять систему, и разберем по шагам, как использование временных таблиц помогает оптимизировать работу базы данных.
Рассмотрим стандартную ситуацию: нам нужно получить остатки товаров только по тем позициям, которые были проданы за определенный период. Часто разработчики пишут один огромный запрос, где в секции ГДЕ или в соединении используется подзапрос к документам продаж. Проблема заключается в том, что оптимизатор SQL-сервера не всегда может эффективно построить план выполнения для таких вложенных конструкций. Это приводит к избыточному сканированию таблиц и перегрузке процессора.
Когда мы используем подзапросы, СУБД вынуждена выполнять их для каждой строки основного запроса или создавать неявные временные объекты в оперативной памяти. Если данных много, ресурсы сервера быстро истощаются. Чтобы избежать этого, мы рассмотрим методику разделения одного сложного запроса на несколько простых с использованием механизма временных таблиц.
Для работы с временными таблицами в коде 1С нам необходимо инициализировать специальный объект — МенеджерВременныхТаблиц. Этот объект выступает контейнером, в котором физически будут храниться данные наших промежуточных вычислений на стороне SQL-сервера в базе tempdb. Проанализируем пример кода для создания менеджера:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Привязка менеджера к запросу позволяет всем последующим пакетам запроса "видеть" таблицы, созданные в предыдущих шагах. Это критически важно для построения цепочки обработки данных.
Теперь посмотрим на пример того, как поместить данные во временную таблицу. Допустим, мы хотим сначала отобрать уникальные номенклатурные позиции. Вместо того чтобы сразу соединять справочник с остатками, мы создадим промежуточный список. Выясним причину, почему это эффективно: во временную таблицу попадет только необходимый минимум данных, что уменьшит объем оперативной памяти, требуемый для последующих соединений.
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Продажи.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_ПроданныеТовары
|ИЗ
| РегистрНакопления.Продажи КАК Продажи
|ГДЕ
| Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода";
Запрос.Выполнить();
Обратите внимание на ключевое слово ПОМЕСТИТЬ. Оно указывает системе, что результат выполнения не нужно возвращать в 1С, а следует сохранить в базе данных под именем ВТ_ПроданныеТовары.
Многие начинающие программисты забывают об индексах, что сводит на нет всю пользу от временных таблиц. Если во временной таблице содержится более нескольких сотен строк, отсутствие индекса приведет к тому, что при соединении с основной таблицей СУБД будет выполнять полное сканирование (Full Scan). Разберем, как добавить индекс в нашу таблицу ВТ_ПроданныеТовары.
Существует два способа индексации:
ИНДЕКСИРОВАТЬ ПО.Давайте проанализируем ситуацию на примере кода с индексацией:
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Продажи.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_ПроданныеТовары
|ИЗ
| РегистрНакопления.Продажи КАК Продажи
|ГДЕ
| Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура";
Наличие индекса по полю Номенклатура позволит СУБД мгновенно находить нужные записи при соединении этой временной таблицы с виртуальными таблицами остатков или другими справочниками.
После того как мы подготовили и проиндексировали промежуточные данные, мы можем переходить к основному запросу. Теперь мы будем соединяться не с огромным регистром продаж, а с маленькой и быстрой таблицей ВТ_ПроданныеТовары. Разберем этот этап подробнее.
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
| ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Остаток
|ИЗ
| ВТ_ПроданныеТовары КАК ПроданныеТовары
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&МоментВремени, ) КАК ТоварыНаСкладахОстатки
| ПО ПроданныеТовары.Номенклатура = ТоварыНаСкладахОстатки.Номенклатура";
Результат = Запрос.Выполнить().Выгрузить();
В данном случае система сначала отработает по индексу временной таблицы, а затем выполнит эффективное соединение с остатками. Это значительно быстрее, чем использование условий В (ВЫБРАТЬ...).
Чтобы наша совместная работа над оптимизацией была максимально эффективной, выясним несколько правил хорошего тона в разработке на 1С:
tempdb. Если данных всего 10-20 строк, обычный подзапрос или передача массива в параметры может быть быстрее.УНИЧТОЖИТЬ в тексте запроса.СоставнойТип, индекс может работать неэффективно.Запрос.Выполнить() для каждой временной таблицы, пишите один текст запроса, где команды разделены символом ";". Это сократит количество обращений к серверу БД.Для закрепления материала рассмотрим подробнее, как выглядит правильно оптимизированный пакетный запрос в 1С:
ТекстЗапроса =
"ВЫБРАТЬ
| ЦеныНоменклатурыСрезПоследних.Номенклатура,
| ЦеныНоменклатурыСрезПоследних.Цена
|ПОМЕСТИТЬ ВТ_Цены
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК ЦеныНоменклатурыСрезПоследних
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Цены.Номенклатура,
| ВТ_Цены.Цена,
| СправочникНоменклатура.Артикул
|ИЗ
| ВТ_Цены КАК ВТ_Цены
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СправочникНоменклатура
| ПО ВТ_Цены.Номенклатура = СправочникНоменклатура.Ссылка";
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("Период", ТекущаяДата());
// Менеджер временных таблиц не нужен, если всё в одном пакете
РезультатПакета = Запрос.ВыполнитьПакет();
Данные = РезультатПакета[1].Выгрузить();
Использование метода ВыполнитьПакет() возвращает массив результатов всех запросов, входящих в пакет. Это позволяет одним "походом" на сервер получить все необходимые данные, максимально используя кэширование и оптимизацию СУБД. Проанализировав данный подход, мы видим, что код стал чище, а выполнение — быстрее.
Таким образом, мы разобрали основной инструмент оптимизации в 1С. Использование Временных Таблиц в сочетании с Индексами и Менеджером Временных Таблиц позволяет переложить тяжелые вычисления на SQL-сервер, обеспечивая комфортную работу пользователей даже в очень крупных базах данных.