Skill v1.0.1
Automated scan100/100+1 new
version: "1.0.1" name: query-optimize description: "MUST use WHEN нужно ускорить существующий запрос или переписать СКД dataset. Provides правила устранения query-in-loop, dot-dereference, виртуальных таблиц без параметров и излишних итогов." target_agents:
- developer-code
- architect
alwaysApply: false
Query Optimize — оптимизация запросов и СКД
Навык для оптимизации существующих запросов и схем компоновки данных. Для написания запросов с нуля — query-patterns. Для СУБД-диагностики (план, блокировки, evidence) — db-performance.
Связь с другими навыками
db-performance ← нижний доказательный слой (СУБД-evidence, план, блокировки)↓ передаёт запрос + причинуquery-optimize ← переписывание (этот навык)↓ использует правила написанияquery-patterns ← базовые паттерны (параметризация, NULL, циклы)
Без db-performance-evidence оптимизация — предположение. Если причина неизвестна — сначала db-performance.
Алгоритм
1. Извлечь запрос и контекст выполнения
- Найти текст запроса:
rg "Запрос.Текст\s*=" --type-add "bsl:*.bsl" -t bsl - Для СКД — найти
.xmlсхемы черезcode-navigation, определить набор данных - Зафиксировать:
- Модуль / СКД-датасет
- Параметры виртуальных таблиц (переданы / не переданы)
- Цепочка временных таблиц
- Вызывающий цикл (есть / нет)
- Ожидаемое количество строк / реальное
2. Проверить метаданные
Прежде чем переписывать — проверить структуру объекта метаданных:
- Регистр: тип (накопления / сведений), периодичность, измерения, ресурсы
- Справочник / документ: реквизиты, табличные части
- Индексированные реквизиты (влияет на применимость условий)
Инструмент: code-navigation → структура объекта.
3. Определить одну причину
Выбрать из категорий (одну на итерацию):
| Причина | Признак | |
|---|---|---|
| Широкое чтение виртуальной таблицы | Остатки() / Обороты() без параметров периода или измерений | |
| Query-in-loop | Запрос внутри Для Каждого / Пока / рекурсии | |
| Dot-dereference без ВЫРАЗИТЬ | Движения.Регистратор.Контрагент при составном типе | |
| Избыточные временные таблицы | Промежуточные таблицы с полным набором полей вместо минимального | |
| Лишние итоги | ИТОГИ в запросе, когда нужна плоская выборка | |
| Фильтрация после соединения | Условия ГДЕ на полях большой таблицы вместо параметров виртуальной таблицы | |
| Неявное умножение строк через JOIN | LEFT JOIN без агрегации дублирует строки | |
| РАЗЛИЧНЫЕ маскирует проблему | ВЫБРАТЬ РАЗЛИЧНЫЕ скрывает лишний JOIN вместо его исправления |
4. Применить правило оптимизации
Для каждой причины — конкретное правило (см. раздел «Правила» ниже).
5. Проверить синтаксис и семантику
- Синтаксис:
v8-runnerпосле любого изменения - Семантику: фильтры
РАЗРЕШЕННЫЕне убирать молча, правила безопасности сохраняются - Для join-изменений: убедиться, что количество строк не изменилось неожиданно
6. Запросить СУБД-верификацию при неочевидном эффекте
Если изменение влияет на план СУБД (индекс, параметры виртуальной таблицы, тип соединения) — запросить у пользователя EXPLAIN / ТЖ до и после. Без измерения — зафиксировать как «ожидаемый эффект, требует проверки».
Правила оптимизации
Виртуальные таблицы: параметры внутрь
Проблема: СУБД вычисляет все данные, потом фильтрует в ГДЕ.
// ПЛОХО — фильтр в WHERE, СУБД читает всё"ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Ост|ГДЕ Ост.Номенклатура В (&Список)"// ХОРОШО — параметры внутри виртуальной таблицы"ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(| &ДатаОстатков,| Номенклатура В (&Список) И Склад = &Склад| ) КАК Ост"
Правило: виртуальная таблица всегда получает параметры периода и измерений. Исключение только при явном обосновании (отчёт по всем складам без фильтра).
Query-in-loop: один запрос + Соответствие
Проблема: N итераций × (латентность + время запроса).
// ПЛОХО — запрос в циклеДля Каждого Строка Из Документ.Товары ЦиклЗапрос.УстановитьПараметр("Ном", Строка.Номенклатура);// ... выполнить запросКонецЦикла;// ХОРОШО — один запрос, Соответствие для доступаМассивНом = Документ.Товары.ВыгрузитьКолонку("Номенклатура");Запрос.УстановитьПараметр("Список", МассивНом);// ... один запросРезСоответствие = Новый Соответствие;Пока Выборка.Следующий() ЦиклРезСоответствие.Вставить(Выборка.Ссылка, Выборка);КонецЦикла;Для Каждого Строка Из Документ.Товары ЦиклДанные = РезСоответствие.Получить(Строка.Номенклатура);КонецЦикла;
Dot-dereference: ВЫРАЗИТЬ для составного типа
Проблема: Движения.Регистратор.Контрагент при составном типе — СУБД делает LEFT JOIN ко всем таблицам составного типа.
// ПЛОХО — N LEFT JOIN по составному типу"ВЫБРАТЬ Движения.Регистратор.Контрагент ..."// ХОРОШО — один JOIN"ВЫБРАТЬ| ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент КАК Контрагент|...|ГДЕ Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг"
Временные таблицы: минимальные поля и ИНДЕКСИРОВАТЬ
// Только поля, нужные следующим этапам"ВЫБРАТЬ| Реализация.Ссылка КАК Документ,| Реализация.Контрагент КАК Контрагент|ПОМЕСТИТЬ втРеализации|...|ИНДЕКСИРОВАТЬ ПО Контрагент" // только поле соединения
Правило: ИНДЕКСИРОВАТЬ ПО для полей, по которым будет JOIN в следующем запросе пакета. Не добавлять индекс на все поля.
Лишние итоги: заменить ИТОГИ на СГРУППИРОВАТЬ ПО
ИТОГИ генерирует дополнительные строки-суммы. Если нужна плоская выборка — использовать СГРУППИРОВАТЬ ПО.
// ИТОГИ нужны только при иерархическом обходе Выбрать(ПоГруппировкам)// Для плоской выборки — только СГРУППИРОВАТЬ ПО"ВЫБРАТЬ Контрагент, СУММА(Сумма) КАК Итог|ИЗ ...|СГРУППИРОВАТЬ ПО Контрагент"
Фильтр на составной тип: IN вместо JOIN + РАЗЛИЧНЫЕ
// ПЛОХО — JOIN умножает строки, РАЗЛИЧНЫЕ скрывает"ВЫБРАТЬ РАЗЛИЧНЫЕ Контрагенты.Ссылка|ИЗ Справочник.Контрагенты КАК Контрагенты| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Реализация КАК Реал| ПО Контрагенты.Ссылка = Реал.Контрагент"// ХОРОШО — подзапрос"ВЫБРАТЬ Контрагенты.Ссылка|ИЗ Справочник.Контрагенты КАК Контрагенты|ГДЕ Контрагенты.Ссылка В| (ВЫБРАТЬ РАЗЛИЧНЫЕ Реал.Контрагент| ИЗ Документ.Реализация КАК Реал)"
СКД: специфика оптимизации
Параметры датасета
- Параметры СКД, передаваемые в виртуальные таблицы датасета, работают так же как параметры запроса: передавать внутрь, не фильтровать через отбор после
- Для условий по периоду регистра — всегда параметризовать в тексте запроса датасета
Ресурсы и вычисляемые поля
- Вычисляемые поля СКД, обращающиеся к другим наборам данных через связи — потенциальный query-in-loop на уровне платформы
- Связи наборов данных (СВЯЗЬ) с условиями без индекса на ведомой стороне — проверить метаданные
Отборы в СКД
- Отборы, накладываемые пользователем через настройки, могут не попадать в параметры виртуальных таблиц — это архитектурное ограничение; задокументировать
- Для критичных фильтров (период, организация) — передавать как параметры запроса датасета, не полагаться только на отборы СКД
Чек-лист ревью запроса
- [ ] Виртуальные таблицы получают параметры периода и измерений (не фильтрация в
ГДЕ) - [ ] Временные таблицы содержат только поля, нужные следующим этапам
- [ ] Поля соединения во временных таблицах проиндексированы (
ИНДЕКСИРОВАТЬ ПО) - [ ] Нет повторяющихся подзапросов и query-in-loop
- [ ] JOIN не умножает строки; итоги и группировки соответствуют бизнес-смыслу
- [ ] Фильтры по дате и организации применены как можно раньше
- [ ] Составные типы раскрыты через
ВЫРАЗИТЬперед dot-dereference - [ ]
ЛЕВОЕ СОЕДИНЕНИЕне превращается вВНУТРЕННЕЕиз-за условия вГДЕ - [ ]
РАЗЛИЧНЫЕне маскирует лишний JOIN - [ ]
РАЗРЕШЕННЫЕи прочие фильтры прав сохранены
Stop rules
- Не убирать `РАЗРЕШЕННЫЕ` без явного согласования безопасности.
- Не рекомендовать индекс без конкретного предиката + write-cost оценки — это задача
db-performance. - Не переписывать несколько причин за один шаг — нельзя измерить вклад каждой.
- Не заменять LEFT JOIN на INNER JOIN без проверки бизнес-требования: все строки нужны или только совпадающие.
- Не переносить оптимизацию по данным одной СУБД на другую без проверки: PostgreSQL и MS SQL Server имеют разные модели планировщика.
depends_on:
- framework/skills/bsl-practices/query-patterns/SKILL.md
- framework/skills/tool-usage/diagnostics/db-performance/SKILL.md
- framework/skills/tool-usage/code-analysis/code-navigation/SKILL.md
- framework/skills/tool-usage/v8-runner/SKILL.md