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