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

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

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

Проблема использования вложенных запросов

Рассмотрим стандартную ситуацию: нам необходимо получить данные из нескольких таблиц, при этом одна из них является результатом другого запроса. Начинающие разработчики часто используют вложенные запросы внутри конструкции ГДЕ или в соединениях СОЕДИНЕНИЕ. Однако такой подход заставляет СУБД (SQL Server или PostgreSQL) выполнять вложенную выборку для каждой строки основной таблицы, что приводит к экспоненциальному росту времени выполнения.

Разберем, почему это происходит. Когда мы пишем вложенный запрос в секции ИЗ, оптимизатор СУБД не всегда может корректно построить план запроса, особенно если вложенный запрос содержит сложные агрегаты или обращения к виртуальным таблицам. В результате вместо эффективного поиска по индексу система может перейти к сканированию всей таблицы (Table Scan).

Решение проблемы через временные таблицы

Для оптимизации таких операций мы рекомендуем использовать объект МенеджерВременныхТаблиц. Этот механизм позволяет сохранить промежуточный результат запроса во временную таблицу в оперативной памяти (или в специальной области СУБД tempdb), проиндексировать его и использовать в последующих запросах в рамках одного сеанса.

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


// Создаем менеджер временных таблиц
МенеджерВТ = Новый МенеджерВременныхТаблиц;

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

Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ТекущаяДата()));
Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(ТекущаяДата()));

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

Обратим внимание на ключевое слово ИНДЕКСИРОВАТЬ ПО. Это критически важный момент. Без индексации временной таблицы последующее соединение с ней будет выполняться медленно, так как СУБД придется каждый раз просматривать ее целиком для поиска соответствия.

Работа с виртуальными таблицами и параметрами

Посмотрим на распространенную ошибку при работе с виртуальными таблицами, такими как Остатки, Обороты или СрезПоследних. Часто условия отбора накладываются в секции ГДЕ уже после получения данных из виртуальной таблицы. Это в корне неверно.

Выясним причину: когда условие стоит в ГДЕ, система сначала извлекает все остатки из базы, а затем фильтрует их. Если же мы передаем условие в параметры виртуальной таблицы, СУБД применяет фильтр непосредственно в момент обращения к таблице индексов, что в десятки раз быстрее.

Рассмотрим правильный вариант запроса для получения остатков по нашей временной таблице ВТ_ПроданныеТовары:


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

Запрос.УстановитьПараметр("Период", ТекущаяДата());
Результат = Запрос.Выполнить().Выгрузить();

Основные правила индексации для повышения скорости

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

  1. Индексируйте поля связи. Все поля, по которым происходит ЛЕВОЕ СОЕДИНЕНИЕ или ВНУТРЕННЕЕ СОЕДИНЕНИЕ, должны быть проиндексированы. В 1С это достигается установкой флажка Индексировать в свойствах реквизита или использованием конструкции ИНДЕКСИРОВАТЬ ПО во временных таблицах.
  2. Порядок полей в составном индексе. Если вы используете поиск по нескольким полям, помните: индекс работает эффективно только если условия накладываются начиная с первого поля индекса без пропусков.
  3. Избегайте функций в условиях. Использование функций типа НАЧАЛОПЕРИОДА(Период, ДЕНЬ) в секции ГДЕ делает невозможным использование индекса по полю Период. Вместо этого заранее рассчитывайте параметры в коде 1С.

Разбор антипаттерна: Соединение с подзапросом

Рассмотрим пример кода, который часто становится причиной "зависания" системы при росте базы. Попробуем выяснить, что здесь не так:


// ПЛОХОЙ ПРИМЕР
Запрос.Текст = 
"ВЫБРАТЬ
|   Заказы.Ссылка
|ИЗ
|   Документ.ЗаказКлиента КАК Заказы
|ГДЕ
|   Заказы.Склад В (
|       ВЫБРАТЬ
|           Склады.Ссылка
|       ИЗ
|           Справочник.Склады КАК Склады
|       ГДЕ
|           Склады.ТипСклада = &Тип
|   )";

В данном случае оптимизатор может выполнить вложенный запрос для каждой строки таблицы Заказы. Чтобы исправить это, мы должны сначала поместить список складов во временную таблицу или использовать внутреннее соединение с предварительно отфильтрованным списком.

Рекомендации по написанию эффективного кода

Разберем по шагам алгоритм написания сложного запроса:

  1. Сначала выделите все "фильтрующие" данные (ссылки на объекты, списки номенклатуры, подразделений) во временные таблицы.
  2. Обязательно проиндексируйте временные таблицы по тем полям, по которым в дальнейшем будет идти соединение.
  3. Используйте только параметры виртуальных таблиц для ограничения выборки из регистров.
  4. Если необходимо соединить две большие таблицы (более 100 000 строк), подумайте о возможности пакетного выполнения запроса или разделения логики на несколько этапов.
  5. Проверяйте типы данных. Соединение по полям разных типов (например, Число и Строка) приведет к полному сканированию таблиц, так как индексы не смогут быть задействованы.

Важно помнить, что объект МенеджерВременныхТаблиц требует явного освобождения ресурсов, если он используется в длительных циклах. Хотя 1С автоматически очищает временные таблицы при уничтожении объекта менеджера, в некоторых случаях полезно использовать конструкцию УНИЧТОЖИТЬ ВТ_ИмяТаблицы внутри текста запроса для экономии оперативной памяти сервера приложений.

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

← На главную