К статье
Практический материал

Разработка и Конфигурация · 11 мин

Когда нужно индексировать временные таблицы в 1С

Разбираем, когда индексировать временные таблицы в запросах 1С, какие поля включать в индекс, как выглядит синтаксис ИНДЕКСИРОВАТЬ ПО и как проверить эффект.

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

Проблема усугубляется тем, что временные таблицы создаются «на лету» и не наследуют индексы исходных таблиц. Даже если вы поместили во временную таблицу данные из хорошо проиндексированного регистра, сама временная таблица в памяти или в tempdb индексов не имеет — пока вы явно не укажете конструкцию ИНДЕКСИРОВАТЬ ПО.

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

Как устроены временные таблицы и почему они тормозят

Когда вы выполняете запрос с конструкцией ПОМЕСТИТЬ ВТ_Данные, платформа создаёт физическую таблицу — в оперативной памяти сервера 1С или в служебной базе tempdb на SQL Server, в зависимости от объёма и настроек. Эта таблица существует, пока жив менеджер временных таблиц, и уничтожается при его удалении или закрытии.

Ключевой момент: у свежесозданной временной таблицы нет ни одного индекса. Когда следующий запрос пакета соединяет её с другой таблицей, СУБД в худшем случае выполняет вложенные циклы с полным сканированием: для каждой строки одной таблицы перебираются все строки другой. При 10 000 строк в каждой таблице это уже до 100 миллионов сравнений.

Признак проблемы легко увидеть в плане запроса (в консоли запросов с выводом плана или через технологический журнал): операции Table Scan по временным таблицам, высокая оценка стоимости соединений Nested Loops при большом числе строк. Если запрос с временными таблицами выполняется секундами, хотя исходные данные отбираются быстро — почти наверняка дело в отсутствии индексов.

Когда индексировать обязательно

Первый и главный случай — временная таблица участвует в соединении с другой таблицей (временной или постоянной) и содержит больше нескольких тысяч строк. Индекс по полям соединения позволяет СУБД использовать поиск по индексу вместо полного перебора, что на больших объёмах ускоряет запрос в десятки и сотни раз.

Второй случай — временная таблица используется в условии ГДЕ Поле В (ВЫБРАТЬ ...) или в соединении несколько раз в разных запросах пакета. Один раз построенный индекс окупается многократным использованием: каждое последующее обращение идёт по индексу.

Третий случай — временная таблица соединяется с большой физической таблицей или виртуальной таблицей регистра. Здесь важно, чтобы индекс был именно у временной таблицы: у регистра свои индексы уже есть, а вот «половинка» соединения без индекса заставит СУБД выбрать неудачный план.

Маршрут решения

Как выбрать действие

Временная таблица соединяется с другой таблицей и содержит тысячи строкДобавьте ИНДЕКСИРОВАТЬ ПО по полям соединения
Временная таблица меньше нескольких сотен строкИндекс не нужен — он только замедлит создание таблицы
Временная таблица используется один раз без соединений и отборовИндексировать не нужно, оставьте как есть
Одна временная таблица используется в нескольких запросах пакетаИндексируйте по полям всех условий, где она участвует

Когда индекс вреден или бесполезен

Индексация — не бесплатная операция. Платформа сначала заполняет временную таблицу, затем строит по ней индекс, и это дополнительное время. Если таблица содержит пару десятков строк (типичный случай — список из трёх выбранных пользователем складов), полное сканирование мгновенно, а построение индекса — чистые накладные расходы.

Бесполезен индекс и тогда, когда временная таблица ни с чем не соединяется и не фильтруется — например, она просто выводится как результат пакета или используется для группировки без связи с другими источниками. Индекс по полям, которые нигде не используются в условиях, не даст ничего, кроме замедления.

Отдельная ошибка — индексировать «про запас» все поля подряд. Чем шире индекс, тем дороже его построение и тем больше памяти он занимает. Кроме того, СУБД может не использовать слишком широкий составной индекс, если условие идёт не по его первым полям.

По каким полям строить индекс

Правило простое: индекс строится по полям, которые фигурируют в условиях соединения (СОЕДИНЕНИЕ ... ПО ВТ.Поле = Таблица.Поле) и в условиях отбора. Если соединение идёт по двум полям — например, по номенклатуре и складу, — в индекс включаются оба, в том же порядке, в котором они наиболее селективны.

Порядок полей в составном индексе имеет значение. Первым ставьте поле с наибольшей селективностью (наибольшим числом различных значений) или то поле, по которому чаще всего идёт самостоятельный отбор. Индекс по полям «Склад, Номенклатура» не поможет запросу, который ищет только по номенклатуре, — а вот обратный порядок частично поможет обоим сценариям.

Если временная таблица участвует в разных соединениях по разным полям, допустимо перечислить их все в одном ИНДЕКСИРОВАТЬ ПО — платформа создаст индекс по совокупности. Но если сценарии принципиально разные, иногда выгоднее пересмотреть структуру пакета запросов, чем строить громоздкий составной индекс.

Пример синтаксиса:

ВЫБРАТЬ

Товары.Номенклатура КАК Номенклатура,

Товары.Склад КАК Склад,

СУММА(Товары.Количество) КАК Количество

ПОМЕСТИТЬ ВТ_Остатки

ИЗ

РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК Товары

СГРУППИРОВАТЬ ПО

Товары.Номенклатура,

Товары.Склад

ИНДЕКСИРОВАТЬ ПО

Номенклатура,

Склад

Как проверить, что индекс сработал

Первый способ — замер времени. Выполните пакет запроса до и после добавления ИНДЕКСИРОВАТЬ ПО на реальном объёме данных (не на пустой тестовой базе). Если время заметно сократилось — индекс работает. Если не изменилось или выросло — индекс либо не используется, либо не нужен.

Второй способ — план запроса. В консоли запросов с показом плана или через средства СУБД (SQL Server Management Studio, pgAdmin для PostgreSQL) проверьте, что по временной таблице вместо Table Scan появился Index Seek. Это прямое подтверждение, что индекс создан и используется оптимизатором.

Третий способ — технологический журнал с событиями DBMSSQL/DBPOSTGRS и анализом длительных запросов. Если после индексации запрос исчез из списка длительных — цель достигнута.

Контрольные точки

Как проверить результат

Время выполнения пакета запроса
Норма

Заметно сократилось на рабочем объёме данных

Тревожный признак

Не изменилось или выросло — индекс лишний или не используется

План запроса по временной таблице
Норма

Операция поиска по индексу (Index Seek)

Тревожный признак

Полное сканирование (Table Scan) — индекс не задействован

Нагрузка на сервер СУБД
Норма

Длительность запроса и число логических чтений снизились

Тревожный признак

Запрос по-прежнему в топе длительных операций

Типичные ошибки при индексации временных таблиц

Частая ошибка — индексировать поле, которое в соединении используется с преобразованием: например, соединение по ПОДСТРОКА(ВТ.Код, 1, 5). Индекс по полю Код в этом случае не сработает, потому что условие применяется к результату функции, а не к полю. Решение — вычислить нужное значение заранее отдельным полем временной таблицы и индексировать его.

Вторая ошибка — забыть, что индекс создаётся в момент выполнения запроса с ПОМЕСТИТЬ. Если временная таблица потом дополняется программно или через ещё один запрос с добавлением строк, индекс может потерять актуальность или не покрывать новые данные в зависимости от реализации СУБД. Надёжнее формировать таблицу целиком одним запросом.

Третья ошибка — слепое копирование индексов из чужих примеров. Состав индекса должен соответствовать вашим условиям соединения, а не условиям из статьи или типового кода.

Типичные ошибки
01
Индексация всех временных таблиц подряд — пакет запросов замедляется из-за лишних построений индексов
02
Индекс по полю, которое в соединении обёрнуто функцией — индекс не используется оптимизатором
03
Составной индекс в неправильном порядке полей — отбор по второму полю индекса не ускоряется
04
Индексация таблицы из десяти строк — накладные расходы без выигрыша
05
Отсутствие замера до и после — невозможно понять, помог индекс или навредил

Альтернативы и дополнительные приёмы

Иногда вместо индексации выгоднее уменьшить саму временную таблицу: отобрать в неё только нужные поля и строки, применив отборы как можно раньше. Меньшая таблица быстрее сканируется даже без индекса, а с индексом — тем более.

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

Также помните про уничтожение временных таблиц (УНИЧТОЖИТЬ ВТ_Данные или МенеджерВременныхТаблиц.Закрыть()): это не ускоряет запрос напрямую, но освобождает память и ресурсы tempdb, что важно при массовых операциях и длительных обработках.

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

FAQ

Нужно ли индексировать временную таблицу, если она используется только в условии В (ВЫБРАТЬ ...)?

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

Можно ли создать несколько индексов для одной временной таблицы?

В языке запросов 1С конструкция ИНДЕКСИРОВАТЬ ПО одна на запрос с ПОМЕСТИТЬ, но в ней можно перечислить несколько полей — будет создан составной индекс. Несколько независимых индексов средствами языка запросов создать нельзя, поэтому порядок полей нужно продумывать под основные сценарии использования таблицы.

Работает ли индекс, если соединение идёт с условием на неравенство?

Как правило, нет или с ограничениями. Индексы эффективны для условий равенства и диапазонов; соединения по условиям «больше/меньше» или «не равно» чаще приводят к сканированию. Если возможно, перестройте логику на соединение по равенству или предварительно ограничьте диапазон отбором.

Что будет, если индексировать временную таблицу, а потом добавить в неё строки другим запросом?

Поведение зависит от СУБД: индекс может обновиться автоматически, но построение индекса при вставке добавит накладных расходов. Надёжная практика — формировать временную таблицу целиком одним запросом с ПОМЕСТИТЬ и не модифицировать её после создания.

Как понять, какие поля включить в индекс, если соединений несколько?

Выпишите все поля временной таблицы, которые встречаются в условиях соединений и отборов по всему пакету запросов. Включите их в ИНДЕКСИРОВАТЬ ПО, поставив первыми наиболее селективные и чаще используемые. После этого проверьте план запроса: если по какому-то условию всё равно идёт сканирование, скорректируйте порядок полей.

Влияет ли индексация временных таблиц на файловый режим работы 1С?

В файловом режиме механизм запросов свой, и эффект от индексации временных таблиц менее выражен, чем в клиент-серверном варианте с полноценной СУБД. Тем не менее синтаксис работает и там, а на больших объёмах разница всё равно заметна. Основную выгоду индексация даёт именно на SQL Server и PostgreSQL.

Итог

Что важно запомнить

Индексируйте временные таблицы только тогда, когда они большие и участвуют в соединениях или отборах. Индекс строится по полям из условий соединения, порядок полей важен. Маленькие таблицы и таблицы без соединений индексировать не нужно. Эффект проверяйте замером времени и планом запроса: Table Scan должен смениться поиском по индексу.