Запрос в 1С: что происходит внутри платформы и СУБД

· 9 мин

Содержание

Запрос на языке запросов 1С сам по себе к базе данных не обращается. Платформа разбирает его текст, переводит в SQL и отдаёт на выполнение серверу СУБД — MS SQL Server, PostgreSQL, Oracle или IBM DB2. План выполнения строит оптимизатор СУБД, а не 1С. В файловой базе эту роль берёт на себя собственный движок платформы.

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

Что происходит с запросом от вызова до выборки

Когда код выполняет Запрос.Выполнить(), платформа проходит несколько стадий:

  1. Разбор и построение. Текст запроса парсится, проверяется синтаксис, подставляются значения параметров (&ПараметрДата и т.п.).
  2. Трансляция в SQL. Платформа разворачивает конструкции языка 1С в SQL конкретной СУБД. На этом шаге виртуальные таблицы превращаются в подзапросы, а имена прикладных объектов — в реальные имена таблиц вида _Reference123 или _AccumRgT456.
  3. Отправка в СУБД. Готовый SQL-текст уходит на сервер базы данных.
  4. Построение плана и выполнение. Оптимизатор СУБД выбирает, какими индексами пользоваться и в каком порядке соединять таблицы, после чего выполняет запрос.
  5. Возврат результата. СУБД отдаёт выборку платформе, та упаковывает её в объект результата запроса и, при необходимости, подгружает представления ссылок.

Имена таблиц СУБД можно сопоставить с объектами конфигурации через функцию ПолучитьСтруктуруХраненияБазыДанных(). Это пригодится, когда в плане запроса видно обращение к _InfoRg789, а понять, какой это регистр, с ходу не получается.

Для файловой базы шагов 3–4 в привычном виде нет: платформа исполняет запрос своим встроенным обработчиком. Поэтому поведение файлового и клиент-серверного вариантов по скорости и блокировкам различается, и тестировать тяжёлые отчёты лучше на той СУБД, что стоит в бою.

Язык запросов и из чего он состоит

Язык запросов 1С декларативный и внешне похож на SQL: ВЫБРАТЬ … ИЗ … ГДЕ … СГРУППИРОВАТЬ ПО. Но работает он не с «голыми» таблицами, а с объектами метаданных, и это даёт несколько вещей, которых в чистом SQL нет.

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

Важная особенность — язык запросов умеет только читать данные. Изменять записи, проводить документы, писать в регистры запросом нельзя: для этого есть объектная модель. Запрос — это всегда выборка.

Временные таблицы заслуживают отдельного слова. Оператор ПОМЕСТИТЬ сохраняет промежуточный результат, а менеджер временных таблиц (МенеджерВременныхТаблиц) позволяет переиспользовать его в нескольких запросах подряд, не пересчитывая заново. На клиент-серверной базе такие таблицы физически создаются в служебной базе tempdb (для MS SQL) и живут, пока жив менеджер. Это основной инструмент, когда одну и ту же выборку нужно соединять с разными данными.

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

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

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

Что реально меняет план в лучшую сторону:

  • Отбор как можно раньше. Условия, отсекающие строки, должны стоять в параметрах виртуальной таблицы, а не в ГДЕ поверх развёрнутой выборки. Для Остатки(&Дата, Склад = &Склад) отбор по складу попадёт внутрь подзапроса к итогам; тот же отбор в ГДЕ отработает уже после разворота — медленнее.
  • Временные таблицы вместо вложенных соединений. Крупный запрос разбивается на пакет: сначала отбираем нужные ключи в ПОМЕСТИТЬ ВТ_Ключи, потом соединяем с ними. Оптимизатору СУБД работать с небольшой временной таблицей проще, чем с громоздким вложенным подзапросом.
  • Отказ от соединений с подзапросами. Соединение ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ …) оптимизатор нередко обрабатывает плохо. Вынесите подзапрос во временную таблицу — и соединяйте уже с ней.
  • Индексирование временных таблиц. В пакете можно указать ИНДЕКСИРОВАТЬ ПО при записи во временную таблицу — это ускорит последующее соединение по тем же полям.

Платформа кэширует планы на стороне СУБД. Если один и тот же запрос с разными параметрами то быстрый, то медленный, причиной бывает «застрявший» план под неудачный набор параметров (parameter sniffing). Лечится это уже средствами СУБД, а не языком запросов.

Индексы: почему от них зависит скорость

Индекс — это структура, которая позволяет СУБД найти нужные строки без полного перебора таблицы. Нет подходящего индекса — сервер читает таблицу целиком (сканирование), и время растёт пропорционально её размеру.

Часть индексов платформа создаёт сама:

  • по измерениям регистров сведений и накопления;
  • по полям, участвующим в поиске (код, наименование справочника, если включён поиск);
  • служебные индексы по ссылкам и периоду.

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

При этом индексы не бесплатны:

Что делает индексПлюсМинус
Ускоряет чтение и отборзапрос находит строки сразузанимает место на диске
Поддерживается при записикаждая запись/проведение чуть медленнее
Помогает соединениямменьше нагрузка на СУБДлишние индексы оптимизатор может игнорировать

Поэтому индексировать всё подряд смысла нет. Индекс оправдан там, где по полю идёт частый отбор в запросах и отчётах, и бесполезен для реквизита, который нигде в условиях не фигурирует.

Блокировки и транзакции при чтении данных

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

  • Управляемый режим. Блокировки на чтение платформа ставит явно — из кода, объектом БлокировкаДанных. Запрос сам по себе данные под собой надолго не держит. Это основной режим для современных конфигураций и больших нагрузок.
  • Автоматический режим. Блокировки ставит СУБД по уровню изоляции транзакции. Читающий запрос может задержать пишущие транзакции и наоборот. На серьёзных объёмах это узкое место.

Внутри транзакции (НачатьТранзакцию() … ЗафиксироватьТранзакцию()) прочитанные и изменённые данные остаются заблокированными до конца транзакции. Чем дольше транзакция, тем выше риск, что два сеанса встанут в очередь друг к другу, а в худшем случае — во взаимоблокировку (deadlock), когда каждый ждёт то, что держит другой. СУБД разрывает deadlock, принудительно откатывая одну из транзакций.

Практические следствия для того, кто пишет запросы:

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

Если в журнале регистрации регулярно встречаются сообщения о превышении времени ожидания на блокировке или о взаимоблокировках, причину почти всегда видно в управляемых блокировках и длинных транзакциях при проведении, а не в самих запросах-отчётах.

Как измерить, где запрос тормозит

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

Замер производительности в конфигураторе. Режим отладки показывает, сколько времени занял каждый оператор встроенного языка. Удобно, чтобы локализовать проблему: сам запрос медленный или окружающий код.

Консоль запросов. Внешняя обработка, в которой можно выполнить запрос, посмотреть результат и, главное, получить его план выполнения. Для быстрой проверки гипотез «а если переписать отбор» это самый короткий путь.

Технологический журнал. Настраивается файлом logcfg.xml. Для разбора запросов интересны события:

  • DBMSSQL — текст SQL, который платформа реально отправила в СУБД, и время его выполнения;
  • Query — выполнение запроса на уровне платформы;
  • TLOCK / TDEADLOCK — установка управляемых блокировок и взаимоблокировки.

По событию DBMSSQL видно и длительность, и число прочитанных строк, и сам SQL — его можно скопировать и прогнать в СУБД.

План выполнения на стороне СУБД. В MS SQL Server это план запроса (графический или SET SHOWPLAN_ALL ON), в PostgreSQL — команда EXPLAIN ANALYZE. В плане видно главное: СУБД ищет строки по индексу (seek) или сканирует таблицу целиком (scan). Сканирование большой таблицы — первый кандидат на оптимизацию.

Центр управления производительностью (ЦУП) и методика APDEX пригодятся, когда нужно оценивать производительность системы в целом, а не один запрос.

Разбор медленного запроса
Выполнено: 0 / 7

Типичные ошибки при написании запросов

Большинство тормозов возникает не из-за объёма данных, а из-за того, как написан запрос. Вот что встречается чаще всего и что с этим делать.

Запрос в цикле. Классика: обходим таблицу значений и для каждой строки выполняем отдельный запрос. Вместо сотен мелких обращений соберите ключи в один запрос с условием В (&МассивКлючей) или через соединение с временной таблицей.

Соединение с виртуальной таблицей. ЛЕВОЕ СОЕДИНЕНИЕ Остатки(…) платформа разворачивает в тяжёлую конструкцию. Вынесите виртуальную таблицу в отдельный запрос пакета через ПОМЕСТИТЬ, а соединяйте уже с результатом.

Отбор поверх виртуальной таблицы. Условие по складу или номенклатуре, поставленное в ГДЕ после разворота Остатки(), заставляет СУБД сначала развернуть всё, а потом отфильтровать. Тот же отбор в параметрах виртуальной таблицы отсекает лишнее сразу.

Обращение через точку к полям составного типа. Если реквизит может ссылаться на несколько типов, каждая точка в запросе порождает соединение со всеми возможными таблицами. Либо явно приводите тип через ВЫРАЗИТЬ(… КАК …), либо получайте нужные поля отдельным запросом.

ВЫБРАТЬ * и лишние поля. Чем больше полей и соединений вы тащите в результат, тем больше работы у СУБД и тем больше представлений ссылок потом читает платформа. Берите только то, что реально используете.

ПОДОБНО с процентом в начале. Шаблон "%текст" не даёт воспользоваться индексом — СУБД перебирает всю таблицу. Поиск по началу строки ("текст%") индекс использует.

Отбор по непроиндексированному полю. Если в ГДЕ стоит условие по реквизиту без индекса, СУБД сканирует таблицу целиком. Решение — индекс на поле отбора (см. раздел про индексы).

Чем язык запросов 1С отличается от SQL?
Язык запросов работает с объектами метаданных и ссылками, умеет виртуальные таблицы, итоги и пакеты, но только читает данные. Платформа переводит его в SQL конкретной СУБД, которая и выполняет запрос.
Можно ли запросом изменить или записать данные?
Нет. Запрос всегда возвращает выборку. Для изменения данных используется объектная модель: запись объектов, наборов записей регистров, проведение документов.
Почему файловая и серверная базы ведут себя по-разному?
В клиент-серверном варианте запрос выполняет СУБД со своим оптимизатором, в файловом — встроенный движок платформы. Планы, индексы и блокировки работают иначе, поэтому тяжёлые отчёты тестируют на той же СУБД, что в эксплуатации.
Где посмотреть, какой SQL платформа отправила в базу?
В событии DBMSSQL технологического журнала: там есть и текст SQL, и время выполнения. Этот SQL можно скопировать и получить его план выполнения уже средствами СУБД.
Что быстрее исправляет медленный запрос — новый индекс или переписывание?
Сначала смотрят план: если СУБД сканирует большую таблицу по неиндексированному отбору — помогает индекс. Если проблема в соединении с виртуальной таблицей или запросе в цикле — переписывание запроса даёт больший эффект.