Что происходит, когда 1С выполняет ВЫБРАТЬ, и как ускорить запрос
Содержание
Запрос в 1С начинается со слова ВЫБРАТЬ: вы перечисляете поля, указываете источник после ИЗ и фильтр после ГДЕ. Но за этим текстом стоит целая цепочка — язык запросов 1С, менеджер запросов на сервере приложений и уже реальный SQL, который выполняет СУБД. Понимание этой цепочки и отделяет запрос, который отдаёт тысячи строк за доли секунды, от того, что вешает базу на минуту.
Чаще всего медленный запрос — это не «много данных», а неудачно написанный текст: отбор мимо индекса, лишние соединения через точку, фильтр не в параметрах виртуальной таблицы. Разберём механизм на реальных объектах — справочниках и регистрах — и те приёмы, которыми разработчик вытягивает производительность.
Из чего состоит запрос на выборку
Минимальный запрос — это ВЫБРАТЬ, список полей и источник:
ВЫБРАТЬ
Номенклатура.Наименование КАК Товар,
Номенклатура.Артикул
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.ПометкаУдаления = ЛОЖЬ
Остальные конструкции наращиваются вокруг этого каркаса:
- 📂
ИЗ— источник данных: таблица справочника, документа, регистра или временная таблица. - 🔍
ГДЕ— отбор строк. Срабатывает после соединений, но до группировки. - 🔗
СОЕДИНЕНИЕ(ЛЕВОЕ,ВНУТРЕННЕЕ,ПОЛНОЕ) — связывает таблицы по условиюПО. - 📊
СГРУППИРОВАТЬ ПО— свёртка строк, вместе с агрегатамиСУММА,КОЛИЧЕСТВО,МАКСИМУМ. - 🎯
ИМЕЮЩИЕ— отбор уже по агрегатам, после группировки. - ↕️
УПОРЯДОЧИТЬ ПО— сортировка результата. ПЕРВЫЕ N— ограничение числа строк,РАЗЛИЧНЫЕ— удаление дублей.
Отдельно стоят ИТОГИ ПО — они считаются уже на стороне сервера приложений, а не в СУБД, и дают иерархический результат. Для простой выборки их обычно не используют: группировку дешевле получить через СГРУППИРОВАТЬ ПО.
Поля удобно получать «через точку»: Номенклатура.ЕдиницаИзмерения.Наименование. Коротко в тексте — но за каждой точкой прячется соединение. Об этом ниже.
Как 1С превращает ВЫБРАТЬ в SQL
Текст на языке запросов 1С сам по себе ничего не выполняет. Когда вы вызываете Запрос.Выполнить(), происходит следующее:
- Менеджер запросов на сервере приложений разбирает текст и строит из него SQL-запрос под конкретную СУБД — MS SQL Server или PostgreSQL.
- Объекты метаданных заменяются именами физических таблиц:
Справочник.Номенклатурапревращается в таблицу вроде_Reference123, поля — в колонки_Fld.... - СУБД получает готовый SQL, строит план выполнения и возвращает выборку серверу приложений.
- Сервер приложений оборачивает результат в объект
РезультатЗапросаи отдаёт его вашему коду.
Из-за этой трансляции язык запросов 1С почти повторяет SQL, но не равен ему. ЛЕВОЕ СОЕДИНЕНИЕ — это LEFT JOIN, СГРУППИРОВАТЬ ПО — GROUP BY. А вот ВЫБРАТЬ РАЗРЕШЕННЫЕ — чисто платформенная вещь: она добавляет в SQL условия ограничения доступа на уровне записей (RLS), которых в исходном тексте не видно.
Запрос в 1С почти всегда выполняется на СУБД, а не в коде. Поэтому оптимизировать нужно текст запроса, а не обработку результата циклом.
Виртуальные таблицы регистров — почему без них выборка медленнее
Регистр накопления физически хранится как таблица движений: приход и расход отдельными строками. Если считать остаток «в лоб» — суммировать все движения с начала времён, — запрос перелопатит миллионы строк.
Платформа предлагает виртуальные таблицы: Остатки, Обороты, ОстаткиИОбороты, у регистров сведений — СрезПоследних и СрезПервых. Это не физические таблицы, а готовые подзапросы, которые платформа строит оптимально, с опорой на служебные таблицы итогов.
Ключевой приём — передавать отбор в параметры виртуальной таблицы, а не в ГДЕ снаружи:
ВЫБРАТЬ
Остатки.Номенклатура,
Остатки.КоличествоОстаток КАК Остаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(
&НаДату,
Склад = &Склад
И Номенклатура В (&СписокТоваров)) КАК Остатки
Здесь период и отбор по складу и номенклатуре уходят внутрь виртуальной таблицы. Платформа применит их до расчёта остатков — выборка окажется минимальной.
Если тот же отбор написать не в параметрах, а в ГДЕ после выборки, остатки сначала посчитаются по всему регистру, и только потом отфильтруются. На большой базе разница — секунды против минут.
У регистров сведений та же логика, но таблица другая. Чтобы получить актуальную цену номенклатуры на дату, берут СрезПоследних:
ВЫБРАТЬ
Цены.Номенклатура,
Цены.Цена
ИЗ
РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
&НаДату,
ТипЦен = &ТипЦен) КАК Цены
СрезПоследних вернёт по одной — последней до указанной даты — записи на каждую номенклатуру. Писать такой отбор вручную через группировку с МАКСИМУМ(Период) и обратное соединение не нужно: платформа строит его сама и делает это эффективнее.
Почему запрос тормозит: индексы и план выполнения
Скорость выборки на СУБД решают индексы. Индекс — это отсортированная структура, которая позволяет найти нужные строки, не читая всю таблицу. У таблиц 1С индексы создаются автоматически: по ссылке, по измерениям регистров, по полям, отмеченным как «Индексировать» в конфигураторе.
Запрос использует индекс, когда условие ГДЕ или ПО опирается на индексированные поля слева направо. У составного индекса (Склад, Номенклатура, Период) отбор по одному складу индекс задействует, а отбор по одной номенклатуре без склада — уже нет.
Как увидеть, что происходит на самом деле:
- 🧪 Замер производительности в конфигураторе (
Отладка → Замер производительности) показывает, какая строка кода и какой запрос съедают время. - 📝 Технологический журнал с событием
DBMSSQLфиксирует реальный SQL и его длительность. - 🔎 В MS SQL Server план выполнения смотрят через SQL Server Profiler или
SHOWPLAN; в PostgreSQL — черезEXPLAIN ANALYZE.
В плане ищут сканирование таблицы целиком (Table Scan, Seq Scan) — верный признак, что индекс не подошёл. Цель — чтобы СУБД делала поиск по индексу (Index Seek), а не перебор.
Второй маркер проблемы — число прочитанных строк намного больше числа строк в результате. Если запрос возвращает 50 строк, а СУБД прочитала 2 миллиона, значит, отбор отработал уже после чтения всей таблицы. Такой запрос и надо переписывать в первую очередь.
Частые ошибки при написании запросов
Большинство «тормозов» — это не объём данных, а несколько повторяющихся промахов.
Получение реквизитов через точку у составного типа. Поле типа «ЛюбаяСсылка» или «Регистратор» через точку (Движение.Регистратор.Дата) платформа раскрывает в соединения со всеми таблицами возможных типов. Один невинный . — десяток LEFT JOIN. Лучше явно привести тип через ВЫРАЗИТЬ(... КАК Документ.РеализацияТоваровУслуг).
Логическое ИЛИ в условиях. Условие Склад = &С1 ИЛИ Склад = &С2 часто мешает СУБД взять индекс. Замена — Склад В (&СписокСкладов) или объединение двух запросов через ОБЪЕДИНИТЬ ВСЕ.
Соединение с вложенным запросом или виртуальной таблицей. СУБД плохо оценивает такие соединения: для подзапроса нет статистики и индексов. Промежуточный результат надёжнее материализовать во временную таблицу.
Отбор функцией над полем. Условие ГОД(Документ.Дата) = 2025 заставляет вычислить функцию для каждой строки — индекс по дате не работает
. Пишите диапазон: Документ.Дата МЕЖДУ &НачалоГода И &КонецГода.
Отбор НЕ В по большому списку. Условие Номенклатура НЕ В (&Список) СУБД считает тяжелее, чем В: приходится проверять отсутствие по всему набору. Если список длинный, его материализуют во временную таблицу и соединяют, оставляя несоединившиеся строки.
Соединение по выражению, а не по полю. Условие ПО ВЫБОР КОГДА ... КОНЕЦ = ... или ПО ВЫРАЗИТЬ(...) = ... лишает соединение индекса — СУБД вычисляет выражение для каждой пары строк. По возможности соединяйте по «чистым» полям таблиц.
Лишние поля и РАЗЛИЧНЫЕ «на всякий случай». Каждое поле — это данные, которые СУБД читает и гонит по сети на сервер приложений. РАЗЛИЧНЫЕ добавляет сортировку и дедупликацию; если дублей и так нет — это пустая работа.
Приёмы оптимизации выборки
Когда запрос уже написан и тормозит, помогает короткий набор проверенных ходов:
- Перенести все отборы в параметры виртуальных таблиц, убрать их из внешнего
ГДЕ. - Оставить в
ВЫБРАТЬтолько те поля, которые реально нужны результату. - Заменить соединения с подзапросами на соединения с временными таблицами.
- Проверить, что условия отбора ложатся на индексированные поля слева направо.
- Убрать неявные соединения — реквизиты через точку заменить явным соединением или
ВЫРАЗИТЬ. - Там, где отбор идёт по большому списку значений, индексировать временную таблицу со списком.
Оптимизируют по данным, похожим на боевые. На пустой тестовой базе любой запрос «летает» — план СУБД зависит от объёма и статистики таблиц.
Пакетные запросы и временные таблицы
Сложную выборку редко удаётся собрать одним запросом без потери скорости. Пакет — это несколько запросов в одном тексте, разделённых ;. Промежуточные результаты складывают во временные таблицы через ПОМЕСТИТЬ.
ВЫБРАТЬ
Товар.Ссылка КАК Номенклатура
ПОМЕСТИТЬ втТовары
ИЗ
Справочник.Номенклатура КАК Товар
ГДЕ
Товар.Родитель = &Группа
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
ВЫБРАТЬ
втТовары.Номенклатура,
Остатки.КоличествоОстаток КАК Остаток
ИЗ
втТовары КАК втТовары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&НаДату, ) КАК Остатки
ПО Остатки.Номенклатура = втТовары.Номенклатура
Первый запрос отбирает нужную номенклатуру и кладёт её во временную таблицу втТовары, сразу с индексом по полю. Второй соединяет уже компактную таблицу с остатками. СУБД работает с несколькими сотнями строк вместо всего справочника.
Чтобы пакет действительно использовал временные таблицы, запросу нужен менеджер временных таблиц:
МВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МВТ;
Запрос.Текст = ТекстПакета;
Результаты = Запрос.ВыполнитьПакет();
ВыполнитьПакет() возвращает массив результатов — по одному на каждый запрос пакета. Пока жив менеджер временных таблиц, созданные таблицы доступны следующим запросам.
Индекс на временной таблице (ИНДЕКСИРОВАТЬ ПО) оправдан, когда по ней идёт соединение или отбор по сотням и более строк. На десятке строк индекс только добавит накладные расходы — его не ставят.
Особенности выборки в разных версиях и режимах 1С
Сам язык запросов на платформе 8.3 стабилен: конструкции ВЫБРАТЬ, виртуальные таблицы, пакеты и временные таблицы работают одинаково во всех типовых конфигурациях — БП 3.0, ЗУП 3.1, УТ 11. Различается не синтаксис, а то, где выполняется запрос.
| Режим работы | Где считается запрос | Что учесть при выборке |
|---|---|---|
| Файловый | Встроенный файловый движок на той же машине | Нет полноценного оптимизатора СУБД; план не посмотреть; большие выборки заметно медленнее |
| Клиент-серверный | MS SQL Server или PostgreSQL | Есть индексы, статистика, план выполнения; оптимизация имеет смысл |
В клиент-серверном варианте выборка выполняется на СУБД, а результат по сети приходит на сервер приложений. Поэтому лишние поля и строки в выборке — это ещё и трафик между сервером 1С и сервером баз данных.
Расширения конфигурации (появились в платформе 8.3.6) позволяют менять запросы типовой конфигурации без снятия её с поддержки — удобно, когда нужно оптимизировать чужой запрос, не трогая оригинал.
Для диагностики на платформе 8.3 используют интерфейс «Такси» и штатные инструменты: замер производительности в конфигураторе и технологический журнал. Старые механизмы версий 8.1–8.2 на актуальной платформе не нужны — всё перечисленное доступно «из коробки».
Частые вопросы
Чем `ВЫБРАТЬ РАЗРЕШЕННЫЕ` отличается от обычного `ВЫБРАТЬ`?
РАЗРЕШЕННЫЕ добавляет к запросу условия ограничения доступа к данным (RLS) и молча исключает записи, которые пользователю видеть нельзя. Без этого слова запрос по записи без прав вернёт ошибку. В коде, где важна полнота данных, РАЗРЕШЕННЫЕ не используют и выполняют под нужными правами.