Как правильно использовать временные таблицы в запросах 1С 8.3 для ускорения работы системы?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с проблемой падения производительности при выполнении сложных запросов. Особенно остро эта проблема проявляется при работе с большими объемами данных, когда в одном запросе используются множественные вложенные подзапросы или сложные соединения. Сегодня мы проанализируем, почему подзапросы могут замедлять систему, и разберем по шагам, как использование временных таблиц помогает оптимизировать работу базы данных.

Почему подзапросы — это не всегда хорошо?

Рассмотрим стандартную ситуацию: нам нужно получить остатки товаров только по тем позициям, которые были проданы за определенный период. Часто разработчики пишут один огромный запрос, где в секции ГДЕ или в соединении используется подзапрос к документам продаж. Проблема заключается в том, что оптимизатор SQL-сервера не всегда может эффективно построить план выполнения для таких вложенных конструкций. Это приводит к избыточному сканированию таблиц и перегрузке процессора.

Когда мы используем подзапросы, СУБД вынуждена выполнять их для каждой строки основного запроса или создавать неявные временные объекты в оперативной памяти. Если данных много, ресурсы сервера быстро истощаются. Чтобы избежать этого, мы рассмотрим методику разделения одного сложного запроса на несколько простых с использованием механизма временных таблиц.

Шаг 1. Использование объекта МенеджерВременныхТаблиц

Для работы с временными таблицами в коде 1С нам необходимо инициализировать специальный объект — МенеджерВременныхТаблиц. Этот объект выступает контейнером, в котором физически будут храниться данные наших промежуточных вычислений на стороне SQL-сервера в базе tempdb. Проанализируем пример кода для создания менеджера:


МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

Привязка менеджера к запросу позволяет всем последующим пакетам запроса "видеть" таблицы, созданные в предыдущих шагах. Это критически важно для построения цепочки обработки данных.

Шаг 2. Создание первой временной таблицы

Теперь посмотрим на пример того, как поместить данные во временную таблицу. Допустим, мы хотим сначала отобрать уникальные номенклатурные позиции. Вместо того чтобы сразу соединять справочник с остатками, мы создадим промежуточный список. Выясним причину, почему это эффективно: во временную таблицу попадет только необходимый минимум данных, что уменьшит объем оперативной памяти, требуемый для последующих соединений.


Запрос.Текст = 
"ВЫБРАТЬ РАЗЛИЧНЫЕ
|	Продажи.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_ПроданныеТовары
|ИЗ
|	РегистрНакопления.Продажи КАК Продажи
|ГДЕ
|	Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода";

Запрос.Выполнить();

Обратите внимание на ключевое слово ПОМЕСТИТЬ. Оно указывает системе, что результат выполнения не нужно возвращать в 1С, а следует сохранить в базе данных под именем ВТ_ПроданныеТовары.

Шаг 3. Индексирование временных таблиц — ключ к скорости

Многие начинающие программисты забывают об индексах, что сводит на нет всю пользу от временных таблиц. Если во временной таблице содержится более нескольких сотен строк, отсутствие индекса приведет к тому, что при соединении с основной таблицей СУБД будет выполнять полное сканирование (Full Scan). Разберем, как добавить индекс в нашу таблицу ВТ_ПроданныеТовары.

Существует два способа индексации:

  1. Автоматическое создание индекса при помещении в таблицу. Для этого в конструкторе запроса на закладке "Дополнительно" нужно указать поля для индексирования.
  2. Явное указание в тексте запроса с помощью предложения ИНДЕКСИРОВАТЬ ПО.

Давайте проанализируем ситуацию на примере кода с индексацией:


Запрос.Текст = 
"ВЫБРАТЬ РАЗЛИЧНЫЕ
|	Продажи.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_ПроданныеТовары
|ИЗ
|	РегистрНакопления.Продажи КАК Продажи
|ГДЕ
|	Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода
|ИНДЕКСИРОВАТЬ ПО
|	Номенклатура";

Наличие индекса по полю Номенклатура позволит СУБД мгновенно находить нужные записи при соединении этой временной таблицы с виртуальными таблицами остатков или другими справочниками.

Шаг 4. Финальный запрос и получение данных

После того как мы подготовили и проиндексировали промежуточные данные, мы можем переходить к основному запросу. Теперь мы будем соединяться не с огромным регистром продаж, а с маленькой и быстрой таблицей ВТ_ПроданныеТовары. Разберем этот этап подробнее.


Запрос.Текст = 
"ВЫБРАТЬ
|	ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
|	ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Остаток
|ИЗ
|	ВТ_ПроданныеТовары КАК ПроданныеТовары
|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&МоментВремени, ) КАК ТоварыНаСкладахОстатки
|		ПО ПроданныеТовары.Номенклатура = ТоварыНаСкладахОстатки.Номенклатура";

Результат = Запрос.Выполнить().Выгрузить();

В данном случае система сначала отработает по индексу временной таблицы, а затем выполнит эффективное соединение с остатками. Это значительно быстрее, чем использование условий В (ВЫБРАТЬ...).

Важные рекомендации при работе с временными таблицами

Чтобы наша совместная работа над оптимизацией была максимально эффективной, выясним несколько правил хорошего тона в разработке на 1С:

Разбор примера с пакетным запросом

Для закрепления материала рассмотрим подробнее, как выглядит правильно оптимизированный пакетный запрос в 1С:


ТекстЗапроса = 
"ВЫБРАТЬ
|	ЦеныНоменклатурыСрезПоследних.Номенклатура,
|	ЦеныНоменклатурыСрезПоследних.Цена
|ПОМЕСТИТЬ ВТ_Цены
|ИЗ
|	РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК ЦеныНоменклатурыСрезПоследних
|ИНДЕКСИРОВАТЬ ПО
|	Номенклатура
|;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ВТ_Цены.Номенклатура,
|	ВТ_Цены.Цена,
|	СправочникНоменклатура.Артикул
|ИЗ
|	ВТ_Цены КАК ВТ_Цены
|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СправочникНоменклатура
|		ПО ВТ_Цены.Номенклатура = СправочникНоменклатура.Ссылка";

Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("Период", ТекущаяДата());
// Менеджер временных таблиц не нужен, если всё в одном пакете
РезультатПакета = Запрос.ВыполнитьПакет();
Данные = РезультатПакета[1].Выгрузить();

Использование метода ВыполнитьПакет() возвращает массив результатов всех запросов, входящих в пакет. Это позволяет одним "походом" на сервер получить все необходимые данные, максимально используя кэширование и оптимизацию СУБД. Проанализировав данный подход, мы видим, что код стал чище, а выполнение — быстрее.

Таким образом, мы разобрали основной инструмент оптимизации в 1С. Использование Временных Таблиц в сочетании с Индексами и Менеджером Временных Таблиц позволяет переложить тяжелые вычисления на SQL-сервер, обеспечивая комфортную работу пользователей даже в очень крупных базах данных.

← На главную