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