Как ускорить медленные запросы с группировкой в 1С

· 9 мин

Содержание

Если отчёт с итогами крутит «часики» минутами, в 9 случаях из 10 виновата не сама группировка, а то, по чему и из чего она считается. Группировка по вычисляемым выражениям, агрегация прямо по таблицам документов вместо регистров и соединение неиндексированных временных таблиц — вот что превращает СГРУППИРОВАТЬ ПО в полное сканирование миллионов строк.

Разберём по шагам, где искать тормоз и чем его лечить: как прочитать план выполнения на стороне СУБД, когда разложить тяжёлый запрос на временные таблицы, почему функция в поле группировки убивает индекс и как заменить агрегацию документов готовыми оборотами регистра. Приёмы работают в клиент-серверном варианте 1С 8.3 на MS SQL Server и PostgreSQL.

С чего начать: замер, а не догадки

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

Три инструмента, которые дают факты:

  • 📊 Замер производительности в Конфигураторе (Отладка → Замер производительности) — показывает, какая строка кода и какой запрос съедают время. Начинать всегда отсюда.
  • 📈 Центр управления производительностью (ЦУП) из состава КИП — собирает долгие запросы с текстом SQL и планом, строит картину по базе за период.
  • 🧰 Технологический журнал (logcfg.xml с событием DBMSSQL) — пишет текст SQL, длительность и, при нужной настройке, план выполнения прямо в лог.

Правило простое: сначала найдите самый дорогой запрос по времени, и только потом оптимизируйте его. Запрос, который выполняется раз в сутки за 10 секунд, трогать незачем, даже если он «некрасивый».

В ЦУП и техжурнале смотрите не только длительность, но и число прочитанных строк (Reads). Запрос на 200 строк результата, который прочитал 4 млн строк, — главный кандидат на переделку.

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

План выполнения — это маршрут, по которому СУБД добывает данные. Именно он показывает, использовался индекс или сервер честно прочитал всю таблицу.

Получить план на MS SQL Server можно так:

  1. Возьмите текст SQL из техжурнала или ЦУП (событие DBMSSQL, поле со SQL-текстом).
  2. Откройте его в SQL Server Management Studio.
  3. Включите Include Actual Execution Plan (Ctrl+M) и выполните запрос на копии базы.

На что смотреть в плане:

  • Table Scan или Clustered Index Scan по большой таблице — красный флаг. Сервер читает таблицу целиком. Нужен индекс или другое условие отбора.
  • Index Seek — хорошо: сервер прыгает сразу к нужным строкам.
  • Толстые стрелки между операторами — это объём строк. Если перед группировкой через соединение «протекает» миллион строк, проблема в соединении, а не в СГРУППИРОВАТЬ ПО.
  • Операторы Hash Match и Sort с большой стоимостью рядом с группировкой — сервер строит хэш-таблицу или сортирует весь поток в памяти/tempdb.

На PostgreSQL аналог — EXPLAIN ANALYZE: ищите Seq Scan (полное сканирование) вместо Index Scan, а также узлы HashAggregate и GroupAggregate с большим числом строк на входе.

Снимайте план на копии базы, а не на рабочей. Выполнение тяжёлого SQL в SSMS на боевом сервере даёт нагрузку и может повлиять на работу пользователей.

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

Один гигантский запрос с несколькими соединениями и вложенными выборками — типичная причина плохого плана: оптимизатор СУБД не угадывает порядок соединений и ошибается с индексами. Декомпозиция через временные таблицы (ПОМЕСТИТЬ) решает это.

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

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

Что здесь важно:

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

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

Почему функция в поле группировки убивает индекс

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

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

Что именно ломает индекс:

  • ПРЕДСТАВЛЕНИЕ(Ссылка) в группировке — заставляет вычислять строковое представление для каждой строки.
  • Приведение типа ВЫРАЗИТЬ(... КАК ...) в поле связи или группировки.
  • ВЫБОР КОГДА ... ТОГДА ... КОНЕЦ как ключ группировки.
  • Функции над датой (НАЧАЛОПЕРИОДА, ДЕНЬ, МЕСЯЦ) в условии отбора по индексированному периоду.
  • Подстроки и склейка строк в ключе группировки.

Как правильно:

  1. Группируйте по «сырой» ссылке (Номенклатура, а не её представление).
  2. Представление и прочие вычисления добавляйте после группировки — во внешнем запросе или соединением со справочником, как в примере выше.
  3. Для отбора по периоду сравнивайте само поле с границами (Период >= &Начало И Период < &Конец), а не оборачивайте его в НАЧАЛОПЕРИОДА.

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

Проверка на практике: уберите из СГРУППИРОВАТЬ ПО все функции и представления, оставьте голые ссылки и ключевые поля. Если запрос резко ускорился — вы нашли виновника.

Считайте по регистрам, а не по документам

Частая первопричина медленной группировки — агрегация прямо по таблицам документов. Отчёт перебирает все проведённые реализации за год и суммирует строки табличных частей. Это дорого и почти всегда лишнее.

В 1С для итогов и оборотов есть регистры накопления. Их виртуальные таблицы отдают уже посчитанные данные:

  • РегистрНакопления.<Имя>.Остатки(&Период, Отбор) — остатки на дату.
  • РегистрНакопления.<Имя>.Обороты(&Начало, &Конец, Периодичность, Отбор) — обороты за период.
  • РегистрНакопления.<Имя>.ОстаткиИОбороты(...) — и то и другое.

Виртуальная таблица оборотов уже агрегирована по измерениям — часть работы, которую вы делали руками через СГРУППИРОВАТЬ ПО, регистр выполнил на этапе проведения и хранит в таблице итогов. Запрос к оборотам по измерению почти всегда быстрее, чем суммирование документов.

Практические приёмы работы с регистрами:

  • 📌 Параметры периода и отбора задавайте в скобках виртуальной таблицы, а не в ГДЕ снаружи. Условие в параметрах виртуальной таблицы отсекает строки до агрегации; условие в ГДЕ — после, когда сервер уже прочитал всё.
  • 📌 Передавайте только нужные измерения в отбор — чем уже выборка, тем меньше считать.
  • 📌 Параметр периодичности в таблице оборотов (Период, День, Месяц) берите минимально детальный под задачу. Запрос с периодичностью Авто отдаёт итог одной строкой на измерение — это дешевле, чем помесячная детализация, если помесячный разрез отчёту не нужен.
  • 📌 Выбирайте в запросе только те поля, которые реально выводятся. Лишнее поле в выборке тянет за собой чтение данных и иногда дополнительное соединение; ВЫБРАТЬ * в сложных запросах — скрытый источник нагрузки.
  • 📌 Если штатного регистра под отчёт нет, а запрос к документам регулярно тяжёлый, подумайте о собственном регистре накопления или сведений, который наполняется при проведении. Разовая переделка механизма окупается на каждом открытии отчёта.

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

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

Индексы и статистика СУБД

Даже правильный запрос тормозит, если под него нет индексов или статистика СУБД устарела и оптимизатор строит план по неверной оценке строк.

Что в вашей зоне ответственности на стороне 1С:

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

Что на стороне администратора СУБД:

  • Регулярное обновление статистики. На MS SQL — UPDATE STATISTICS либо включённое авто-обновление; на PostgreSQL — ANALYZE (как правило, через autovacuum).
  • Перестроение и дефрагментация индексов (REBUILD / REORGANIZE на MS SQL).

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

Если после обновления статистики план запроса стал другим и быстрее — вы уткнулись именно в устаревшую статистику. Это частая причина «вчера летало, сегодня висит».

Тяжёлые отчёты — в фон и в кэш

Когда запрос оптимизирован, но данных объективно много, остаётся снять ожидание с пользователя: считать заранее и отдавать готовое.

Рабочие варианты:

  • 🔁 Фоновое задание. Длинный отчёт запускайте через ФоновыеЗадания — пользователь не ждёт у замороженной формы, результат приходит по готовности. Для регулярных сводов — по расписанию через регламентное задание ночью.
  • 💾 Предрасчёт в регистр сведений . Если отчёт строится по одним и тем же срезам, посчитайте их регламентным заданием и сложите в периодический регистр сведений. Открытие отчёта превращается в быстрое чтение готовой таблицы.
  • 🧮 Кэш вычислений в коде. Для справочных значений, которые читаются многократно в рамках вызова, помогает ПовторноеИспользованиеВозвратныхЗначений у модуля. Это кэш результата функции, а не замена оптимизации самого запроса.

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

Выбор между «ускорить запрос» и «посчитать заранее» зависит от того, нужны ли данные в реальном времени. Оперативный остаток считайте запросом к регистру; годовой аналитический свод, который смотрят утром, — предрасчётом в фоне.

Чек-лист ускорения запроса с группировкой
Выполнено: 0 / 8

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

Группировка в 1С медленнее, чем GROUP BY в чистом SQL?
Нет. СГРУППИРОВАТЬ ПО транслируется в обычный GROUP BY СУБД. Разница в скорости — от того, по чему вы группируете и из какой таблицы: агрегация документов против готовых оборотов регистра даёт кратную разницу при том же синтаксисе.
Почему запрос быстрый в консоли запросов, но тормозит в отчёте?
Чаще всего в отчёте другие параметры периода и отбора, больше соединений со справочниками ради представлений, либо другой объём данных у конкретного пользователя. Снимите план именно боевого SQL из техжурнала, а не тестового запроса из консоли.
Помогает ли `РАЗРЕШЕННЫЕ` или `РАЗЛИЧНЫЕ` ускорить группировку?
РАЗЛИЧНЫЕ может и замедлить — это дополнительная операция устранения дублей. РАЗРЕШЕННЫЕ влияет на применение RLS, а не на скорость группировки. На производительность сильнее действуют источник данных, индексы и функции в ключах.
Нужно ли всегда индексировать временные таблицы?
Индексируйте те, что участвуют в соединениях или по которым идёт отбор во втором пакете запроса. Для маленькой таблицы на пару сотен строк, которая никуда не соединяется, индекс избыточен.
Что делать, если переписать запрос нельзя — он в типовой конфигурации?
Сначала обновите статистику СУБД и проверьте индексы: часто этого достаточно. Если проблема в типовом отчёте, переносите расчёт в фоновое задание или собственный регистр через расширение конфигурации, не меняя типовой код напрямую.