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

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

Что быстрее в 1С: вложенный запрос или временная таблица

Разбираем, что быстрее в 1С: вложенный запрос или временная таблица. План запроса, индексы, типовые ошибки и практические рекомендации по оптимизации.

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

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

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

Как СУБД исполняет вложенный запрос и временную таблицу

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

Временная таблица работает иначе: вы явно выполняете запрос с конструкцией ПОМЕСТИТЬ ВТ_Имя, результат физически записывается во временное хранилище СУБД (в MS SQL Server — в tempdb). Дальше вы соединяетесь с этой таблицей как с обычной, и если создали по ней индекс — соединение идёт быстро.

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

Когда временная таблица быстрее

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

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

Третий случай — фильтрация огромной таблицы по небольшому списку значений. Классический приём: поместить таблицу значений или результат отбора во временную таблицу с индексом и соединиться с регистром через неё, после чего работать только с уже отфильтрованными данными. Это стандартный паттерн оптимизации отчётов в 1С.

bsl
ВЫБРАТЬ РАЗЛИЧНЫЕ
	Товары.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Товары
ИЗ
	&Товары КАК Товары

ИНДЕКСИРОВАТЬ ПО
	Номенклатура
;

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

Когда вложенный запрос допустим и даже удобнее

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

Вложенный запрос уместен, когда промежуточный результат гарантированно мал (единицы или десятки строк) и используется один раз. Городить менеджер временных таблиц ради такого случая — избыточная сложность кода без выигрыша в скорости.

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

Сравнение подходов

Вложенный запрос

Подходит для простых одноразовых фильтров и малого промежуточного результата. Ограничение: план исполнения не контролируется, при многократном использовании может вычисляться повторно.

Временная таблица с индексом

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

Пакетный запрос с несколькими ВТ

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

Как проверить, что быстрее именно у вас

Единственный надёжный способ — замер на реальных данных. Тесты на демобазе с сотней записей не покажут ничего: на малых объёмах любой план быстрый. Возьмите копию рабочей базы или базу сопоставимого объёма.

Для замера используйте консоль запросов с выводом времени выполнения или инструменты разработчика (ЕДТ) с замером длительности. Для глубокого анализа на клиент-серверном варианте включите технологический журнал с событиями DBMSSQL/DBPOSTGRS и SDBL — так вы увидите фактический текст запроса к СУБД и его длительность.

На MS SQL Server полезно посмотреть фактический план выполнения в Management Studio: ищите операции Scan там, где ожидается Seek, и «толстые» стрелки с большим числом строк на входе соединений. Это прямые признаки того, что вложенный запрос исполняется неудачно и его стоит заменить временной таблицей.

Порядок оптимизации запроса
  1. 1
    Воспроизведите медленное выполнение на копии рабочей базы и зафиксируйте время
  2. 2
    Найдите в запросе вложенные подзапросы и виртуальные таблицы без параметров
  3. 3
    Вынесите повторно используемые и крупные промежуточные результаты во временные таблицы
  4. 4
    Добавьте ИНДЕКСИРОВАТЬ ПО по полям соединений и условий
  5. 5
    Перенесите отборы в параметры виртуальных таблиц
  6. 6
    Повторите замер и сравните время; при возможности сверьте план запроса в СУБД

Типичные ошибки, которые сводят оптимизацию на нет

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

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

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

Типичные ошибки
01
Временная таблица без ИНДЕКСИРОВАТЬ ПО — соединение идёт полным перебором, выигрыша нет
02
Помещение в ВТ лишних полей и строк — растут затраты на материализацию и чтение
03
Условия на виртуальную таблицу в секции ГДЕ вместо параметров — фильтрация после выборки всех данных
04
Дублирование одного вложенного запроса в нескольких местах — он может вычисляться многократно
05
Соединение по вычисляемому выражению или функции от поля — индекс не используется
06
Накопление десятков неиспользуемых ВТ в менеджере — лишняя нагрузка на tempdb, используйте УНИЧТОЖИТЬ

Ограничения и риски

Временные таблицы расходуют ресурсы СУБД: на MS SQL Server это tempdb, на PostgreSQL — временные файлы и память. Десятки параллельно работающих пользователей с тяжёлыми отчётами, каждый из которых строит крупные ВТ, могут упереться в дисковую подсистему. Поэтому оптимизация «всё перевести на ВТ» без анализа нагрузки — плохая идея.

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

Отдельный риск — изменение запроса в рабочей конфигурации без тестирования на копии. Запрос, который стал быстрее на одних данных, может оказаться медленнее на других (например, при другом распределении по контрагентам). Всегда проверяйте на данных, близких к боевым, и сохраняйте возможность отката.

Практические рекомендации по выбору

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

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

Индексируйте ВТ по тем полям, которые участвуют в соединениях и условиях дальше по пакету. Если соединений по разным полям несколько, ориентируйтесь на самое тяжёлое из них — несколько индексов по одной ВТ создавать нельзя.

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

Как выбрать подход

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

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

Время выполнения на копии рабочей базы
Норма

Заметное снижение при том же результате выборки

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

Время не изменилось или выросло — план не улучшился

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

Наборы строк идентичны

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

Результат отличается — изменение запроса сломало логику

План запроса в СУБД
Норма

Index Seek по временным таблицам, узкие соединения

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

Scan по крупным таблицам, соединения без индекса

Нагрузка на tempdb при параллельной работе
Норма

Стабильная при типичном числе пользователей

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

Рост ожиданий диска при массовом запуске отчёта

Итог

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

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

Частые вопросы

Всегда ли временная таблица быстрее вложенного запроса?

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

Обязательно ли писать ИНДЕКСИРОВАТЬ ПО у временной таблицы?

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

Много временных таблиц в одном пакете — это плохо?

Само по себе нет: пакет из 5–10 ВТ — нормальная практика для сложных отчётов. Плохо, когда ВТ создаются «про запас» и не используются или содержат лишние поля и строки. Неиспользуемые таблицы уничтожайте оператором УНИЧТОЖИТЬ.

Как передать таблицу значений в запрос и быстро соединить с регистром?

Передайте таблицу значений как параметр, поместите её во временную таблицу с индексом по ключевому полю, а затем используйте эту ВТ в условии параметра виртуальной таблицы регистра. Это стандартный и самый быстрый способ отфильтровать большой регистр по произвольному списку.

Почему запрос с ВТ в консоли быстрый, а в отчёте СКД медленный?

СКД дорабатывает запрос: добавляет поля, соединения, итоги и может оборачивать ваш пакет в дополнительные уровни вложенности, из-за чего план меняется. Проверяйте итоговый текст запроса, который реально уходит в СУБД, через технологический журнал.

Отличается ли поведение на PostgreSQL и MS SQL Server?

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