Разработка и Конфигурация · 12 мин
Что быстрее в 1С: вложенный запрос или временная таблица
Разбираем, что быстрее в 1С: вложенный запрос или временная таблица. План запроса, индексы, типовые ошибки и практические рекомендации по оптимизации.
Вопрос о том, что быстрее — вложенный запрос или временная таблица, возникает у разработчиков 1С всякий раз, когда отчёт или обработка начинает «тормозить» на больших объёмах данных. Однозначного ответа «всегда используйте X» не существует: производительность зависит от плана запроса, который построит СУБД, от наличия индексов, объёма промежуточных данных и структуры самого запроса.
Тем не менее практика показывает устойчивую закономерность: временные таблицы с явным созданием индексов почти всегда выигрывают у глубоко вложенных запросов, особенно когда промежуточный результат используется несколько раз или соединяется с большими таблицами. Вложенный запрос оптимизатор может «развернуть» неудачно — и тогда соединение выполняется без индекса, с полным перебором.
Ниже разберём, как СУБД исполняет оба варианта, в каких сценариях выигрывает каждый подход, как проверить реальную скорость на своих данных и какие ошибки чаще всего приводят к медленным запросам.
Как СУБД исполняет вложенный запрос и временную таблицу
Вложенный запрос (подзапрос в секции ИЗ или В) остаётся частью одного большого запроса. Оптимизатор СУБД решает, как его выполнить: материализовать результат во временную структуру, «развернуть» в соединение или вычислять для каждой строки внешнего запроса. Разработчик на это решение почти не влияет, и на разных версиях платформы и СУБД план может отличаться.
Временная таблица работает иначе: вы явно выполняете запрос с конструкцией ПОМЕСТИТЬ ВТ_Имя, результат физически записывается во временное хранилище СУБД (в MS SQL Server — в tempdb). Дальше вы соединяетесь с этой таблицей как с обычной, и если создали по ней индекс — соединение идёт быстро.
Ключевое отличие — контроль. С временной таблицей вы сами решаете, что материализуется, когда и с каким индексом. С вложенным запросом всё отдаёте на откуп оптимизатору, который на сложных конструкциях из трёх-четырёх уровней вложенности нередко строит неоптимальный план.
Когда временная таблица быстрее
Первый и главный случай — промежуточный результат используется в запросе несколько раз. Вложенный запрос, написанный дважды, с высокой вероятностью будет вычислен дважды. Временная таблица материализуется один раз, и все последующие обращения к ней практически бесплатны.
Второй случай — соединение промежуточного результата с большой физической таблицей, например с регистром накопления на десятки миллионов записей. Если промежуточные данные не проиндексированы, СУБД может выбрать соединение вложенными циклами с полным сканированием. Временная таблица с конструкцией ИНДЕКСИРОВАТЬ ПО решает эту проблему явно.
Третий случай — фильтрация огромной таблицы по небольшому списку значений. Классический приём: поместить таблицу значений или результат отбора во временную таблицу с индексом и соединиться с регистром через неё, после чего работать только с уже отфильтрованными данными. Это стандартный паттерн оптимизации отчётов в 1С.
ВЫБРАТЬ РАЗЛИЧНЫЕ
Товары.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Товары
ИЗ
&Товары КАК Товары
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(
&Период,
Номенклатура В (ВЫБРАТЬ Т.Номенклатура ИЗ ВТ_Товары КАК Т)) КАК ОстаткиКогда вложенный запрос допустим и даже удобнее
Для простого одноразового фильтра вложенный запрос короче и читаемее. Конструкция вида ГДЕ Номенклатура В (ВЫБРАТЬ ...) по небольшой таблице обычно не создаёт проблем: оптимизатор легко превращает её в полусоединение, и разницы с временной таблицей вы не заметите.
Вложенный запрос уместен, когда промежуточный результат гарантированно мал (единицы или десятки строк) и используется один раз. Городить менеджер временных таблиц ради такого случая — избыточная сложность кода без выигрыша в скорости.
Важно понимать, что виртуальные таблицы регистров в 1С сами по себе являются скрытыми вложенными запросами. Поэтому параметры виртуальных таблиц (период, условия) нужно задавать в параметрах, а не в секции ГДЕ — иначе фильтрация произойдёт после выборки всех данных, и никакая временная таблица это не спасёт.
Сравнение подходов
Подходит для простых одноразовых фильтров и малого промежуточного результата. Ограничение: план исполнения не контролируется, при многократном использовании может вычисляться повторно.
Подходит для многократного использования результата и соединений с большими таблицами. Ограничение: затраты на материализацию, поэтому на крошечных данных выигрыша нет.
Подходит для сложных отчётов с поэтапной фильтрацией. Ограничение: требует аккуратного уничтожения неиспользуемых ВТ и продуманных индексов.
Как проверить, что быстрее именно у вас
Единственный надёжный способ — замер на реальных данных. Тесты на демобазе с сотней записей не покажут ничего: на малых объёмах любой план быстрый. Возьмите копию рабочей базы или базу сопоставимого объёма.
Для замера используйте консоль запросов с выводом времени выполнения или инструменты разработчика (ЕДТ) с замером длительности. Для глубокого анализа на клиент-серверном варианте включите технологический журнал с событиями DBMSSQL/DBPOSTGRS и SDBL — так вы увидите фактический текст запроса к СУБД и его длительность.
На MS SQL Server полезно посмотреть фактический план выполнения в Management Studio: ищите операции Scan там, где ожидается Seek, и «толстые» стрелки с большим числом строк на входе соединений. Это прямые признаки того, что вложенный запрос исполняется неудачно и его стоит заменить временной таблицей.
- 1Воспроизведите медленное выполнение на копии рабочей базы и зафиксируйте время
- 2Найдите в запросе вложенные подзапросы и виртуальные таблицы без параметров
- 3Вынесите повторно используемые и крупные промежуточные результаты во временные таблицы
- 4Добавьте ИНДЕКСИРОВАТЬ ПО по полям соединений и условий
- 5Перенесите отборы в параметры виртуальных таблиц
- 6Повторите замер и сравните время; при возможности сверьте план запроса в СУБД
Типичные ошибки, которые сводят оптимизацию на нет
Самая частая ошибка — создать временную таблицу, но забыть индекс. Без ИНДЕКСИРОВАТЬ ПО соединение с ней на больших объёмах мало чем отличается от вложенного запроса, а накладные расходы на материализацию уже заплачены.
Вторая ошибка — помещать во временную таблицу лишние поля и строки. Чем шире и длиннее ВТ, тем дороже её запись и чтение. Выбирайте только поля, нужные для соединения и итоговой выборки, и применяйте отборы как можно раньше.
Третья ошибка — соединение с виртуальной таблицей без отбора слева. Если вы соединяете свою ВТ с остатками регистра, но не ограничили виртуальную таблицу параметром по тем же измерениям, СУБД сначала вычислит остатки по всей таблице, а потом отфильтрует. Правильный приём — передать ВТ в параметр условия виртуальной таблицы.
Ограничения и риски
Временные таблицы расходуют ресурсы СУБД: на MS SQL Server это tempdb, на PostgreSQL — временные файлы и память. Десятки параллельно работающих пользователей с тяжёлыми отчётами, каждый из которых строит крупные ВТ, могут упереться в дисковую подсистему. Поэтому оптимизация «всё перевести на ВТ» без анализа нагрузки — плохая идея.
В файловом варианте 1С механика временных таблиц своя, и выигрыш от индексов ВТ может быть менее выраженным. Выводы, сделанные для клиент-серверной базы, не переносите на файловую без проверки.
Отдельный риск — изменение запроса в рабочей конфигурации без тестирования на копии. Запрос, который стал быстрее на одних данных, может оказаться медленнее на других (например, при другом распределении по контрагентам). Всегда проверяйте на данных, близких к боевым, и сохраняйте возможность отката.
Практические рекомендации по выбору
Рабочее эмпирическое правило: один уровень вложенности для простого фильтра — оставляйте вложенный запрос; два и более уровня, повторное использование результата или соединение с крупной таблицей — выносите во временную таблицу с индексом. Это покрывает большинство реальных ситуаций.
Проектируйте запрос по принципу «сужающейся воронки»: каждая следующая временная таблица должна содержать меньше строк, чем предыдущая выборка из физических таблиц. Тогда финальные соединения работают с малыми объёмами и выполняются быстро независимо от капризов оптимизатора.
Индексируйте ВТ по тем полям, которые участвуют в соединениях и условиях дальше по пакету. Если соединений по разным полям несколько, ориентируйтесь на самое тяжёлое из них — несколько индексов по одной ВТ создавать нельзя.
Как выбрать подход
Как проверить результат оптимизации
Заметное снижение при том же результате выборки
Время не изменилось или выросло — план не улучшился
Наборы строк идентичны
Результат отличается — изменение запроса сломало логику
Index Seek по временным таблицам, узкие соединения
Scan по крупным таблицам, соединения без индекса
Стабильная при типичном числе пользователей
Рост ожиданий диска при массовом запуске отчёта
Что важно запомнить
Временная таблица с индексом быстрее и предсказуемее в тяжёлых сценариях: многократное использование результата и соединения с большими таблицами. Вложенный запрос приемлем для простых одноразовых фильтров. Окончательный ответ даёт только замер на реальных данных и анализ плана запроса.
Частые вопросы
Всегда ли временная таблица быстрее вложенного запроса?
Нет. На малых объёмах и простых фильтрах разницы нет, а иногда вложенный запрос даже выигрывает за счёт отсутствия затрат на материализацию. Преимущество ВТ проявляется на больших данных, повторном использовании результата и соединениях с крупными таблицами.
Обязательно ли писать ИНДЕКСИРОВАТЬ ПО у временной таблицы?
Если таблица используется в соединениях или условиях и содержит больше нескольких десятков строк — да, это главный источник выигрыша. Для крошечной ВТ из нескольких строк индекс не обязателен, но и не мешает.
Много временных таблиц в одном пакете — это плохо?
Само по себе нет: пакет из 5–10 ВТ — нормальная практика для сложных отчётов. Плохо, когда ВТ создаются «про запас» и не используются или содержат лишние поля и строки. Неиспользуемые таблицы уничтожайте оператором УНИЧТОЖИТЬ.
Как передать таблицу значений в запрос и быстро соединить с регистром?
Передайте таблицу значений как параметр, поместите её во временную таблицу с индексом по ключевому полю, а затем используйте эту ВТ в условии параметра виртуальной таблицы регистра. Это стандартный и самый быстрый способ отфильтровать большой регистр по произвольному списку.
Почему запрос с ВТ в консоли быстрый, а в отчёте СКД медленный?
СКД дорабатывает запрос: добавляет поля, соединения, итоги и может оборачивать ваш пакет в дополнительные уровни вложенности, из-за чего план меняется. Проверяйте итоговый текст запроса, который реально уходит в СУБД, через технологический журнал.
Отличается ли поведение на PostgreSQL и MS SQL Server?
Да, оптимизаторы разные, и один и тот же запрос может получить разные планы. Общие принципы (индексы ВТ, ранняя фильтрация, параметры виртуальных таблиц) работают на обеих СУБД, но конкретные замеры нужно делать на той СУБД, которая используется в production.