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

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

Как ускорить работу запроса в 1С

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

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

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

Как найти узкое место перед оптимизацией

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

Для MS SQL Server используйте SQL Server Profiler или Extended Events, чтобы увидеть, какой именно SQL-запрос генерирует платформа и сколько он выполняется. Для PostgreSQL подойдёт pg_stat_statements и команда EXPLAIN ANALYZE. План запроса покажет, есть ли сканирование всей таблицы (Table Scan / Seq Scan) там, где ожидался поиск по индексу (Index Seek).

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

Запрос выполняется секунды при малом объёме данныхВероятно, нет подходящего индекса или условие не индексируемое

План запроса: ищите Table Scan вместо Index Seek

Запрос быстрый, но операция медленнаяЗапрос вызывается многократно в цикле

Количество вызовов запроса в замере производительности

Запрос тормозит только с определёнными параметрамиПроблема с распределением данных или параметризацией

Время выполнения при разных значениях параметров

Фильтруйте данные как можно раньше

Главный принцип быстрого запроса — обрабатывать минимум строк. Все ограничения, которые можно выразить через ГДЕ, должны быть в секции ГДЕ, а не в секции ИМЕЮЩИЕ и не в постобработке на встроенном языке. Чем раньше СУБД отбросит лишние строки, тем меньше данных участвует в соединениях, группировках и сортировке.

Типичная ошибка — выбрать весь регистр за всё время, а потом в коде пропускать ненужные строки. Если вам нужны данные за месяц по одной организации, оба условия обязаны попасть в запрос: ГДЕ Период МЕЖДУ &Начало И &Конец И Организация = &Организация. То же касается виртуальных таблиц: параметры отборов (период, условия) передавайте в параметры виртуальной таблицы, а не в общее условие запроса — это принципиально влияет на скорость.

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

Используйте индексируемые поля в условиях

Условие ускоряет запрос только тогда, когда по нему СУБД может найти строки через индекс. В 1С индексы создаются автоматически по измерениям регистров, ссылкам, реквизитам с признаком «Индексировать», а также по полям, входящим в критерии отбора. Если в условии стоит поле без индекса, СУБД вынуждена читать всю таблицу.

Даже индексированное поле перестаёт работать, если над ним выполняется преобразование. Условия вида ГДЕ ГОД(Период) = 2026 или ГДЕ ПОДСТРОКА(Номенклатура.Наименование, 1, 5) = "Стол" отключают индекс — функция применяется к каждой строке. Вместо ГОД(Период) используйте диапазон Период МЕЖДУ &НачалоГода И &КонецГода.

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

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

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

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

bsl
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
| Обороты.Номенклатура КАК Номенклатура,
| Обороты.КоличествоОборот КАК Количество
|ПОМЕСТИТЬ ВТ_Обороты
|ИЗ
| РегистрНакопления.Продажи.Обороты(&Начало, &Конец, , Организация = &Организация) КАК Обороты
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Товары.Ссылка КАК Товар,
| ВТ_Обороты.Количество КАК Количество
|ИЗ
| Справочник.Номенклатура КАК Товары
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Обороты КАК ВТ_Обороты
| ПО Товары.Ссылка = ВТ_Обороты.Номенклатура";
Результат = Запрос.Выполнить().Выгрузить();

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

Уберите лишние поля, соединения и итоги

Каждое поле в выборке — это данные, которые нужно прочитать, передать и обработать. Конструкция ВЫБРАТЬ * или выборка «на всякий случай» тридцати полей, из которых используются пять, увеличивает и время выполнения, и сетевой трафик в клиент-серверном варианте, и расход памяти при выгрузке в таблицу значений.

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

Итоги и группировки тоже не бесплатны. Если нужна только детальная выборка, уберите ИТОГИ — платформа не будет строить дополнительные уровни группировок. Если нужны только итоги по группам, не выбирайте детальные поля: агрегируйте сразу через СУММА, КОЛИЧЕСТВО с СГРУППИРОВАТЬ ПО.

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

Применяйте пакетные запросы и правильную обработку результата

Если логика требует нескольких обращений к одним и тем же данным, объединяйте их в пакетный запрос через менеджер временных таблиц вместо нескольких отдельных запросов. Каждое обращение к СУБД — это сетевой обмен и разбор запроса; пакет выполняется за один сеанс связи.

На стороне обхода результата выбирайте подходящий метод. Выгрузить() в таблицу значений удобен для произвольного доступа, но требует памяти на весь результат. Для больших выборок, которые обрабатываются последовательно, используйте Выбрать() с обходом через Следующий() — так в памяти находится только текущая строка. Обход с ОбходРезультатаЗапроса.ПоГруппировкам при наличии итогов эффективнее ручной группировки в коде.

Параметры запроса передавайте явно и с правильными типами. Если параметр — массив или список значений для условия В (&Список), убедитесь, что он не пустой: условие В () с пустым списком в некоторых сценариях ведёт себя неочевидно, а проверка на неопределённость в коде обходится дёшево.

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

Проверьте результат и учтите ограничения

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

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

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

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

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

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

Время заметно снизилось при тех же входных данных

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

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

Состав и итоги результата
Норма

Строки и суммы совпадают с исходным вариантом

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

Пропали строки или изменились суммы — ошибка в переделанном запросе

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

Index Seek по ключевым таблицам, нет массовых сканирований

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

Остались Table Scan по большим таблицам

Поведение под нагрузкой
Норма

Запрос стабилен при параллельной работе пользователей

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

Возникают блокировки и таймауты

Часто задаваемые вопросы

Что быстрее: соединение или вложенный запрос в условии?

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

Помогает ли РАЗЛИЧНЫЕ ускорить запрос?

Нет, РАЗЛИЧНЫЕ — это дополнительная операция устранения дубликатов, которая сама по себе стоит времени. Если дубликаты появляются из-за соединения «один ко многим», правильнее исправить соединение или сгруппировать данные, а не маскировать проблему различными.

Почему запрос в консоли быстрый, а в работе медленный?

Чаще всего причины такие: в реальном коде запрос выполняется многократно в цикле, параметры отличаются (например, шире период), либо в рабочей базе запрос конкурирует с другими сеансами и упирается в блокировки. Замеряйте именно прикладной сценарий, а не изолированный запрос.

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

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

Влияет ли АВТОУПОРЯДОЧИВАНИЕ на скорость?

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

Когда проблему не решить текстом запроса?

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

Итог

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

Ускорение запроса в 1С начинается с замера и плана выполнения, а не с переписывания текста. Основные приёмы: ранняя фильтрация через условия и параметры виртуальных таблиц, индексируемые поля без функций над ними, временные таблицы вместо вложенных запросов, минимум полей и соединений. Результат проверяйте повторным замером и сверкой данных, а структурные изменения выполняйте только с резервной копией.