Почему запрос к регистрам работает медленно: Разбираем ошибку использования ГДЕ вместо параметров виртуальных таблиц

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С:Предприятие одной из самых распространенных причин низкой производительности отчетов и обработок является неправильное написание запросов к виртуальным таблицам. Часто разработчики, особенно на начальном этапе, сталкиваются с ситуацией, когда простой, казалось бы, запрос к регистру накопления или бухгалтерии выполняется непозволительно долго.

Разберем эту ситуацию подробно, проанализируем причины замедления и рассмотрим правильные методы оптимизации, опираясь на обсуждение проблемы и технические стандарты платформы.

Суть проблемы: Отбор данных после их получения

Рассмотрим типичный сценарий. Нам необходимо получить обороты продаж по конкретному контрагенту за определенный период. Интуитивно разработчик может написать запрос, используя конструкцию ГДЕ (WHERE), воспринимая виртуальную таблицу как обычную таблицу базы данных.

Давайте посмотрим на пример кода, который часто вызывает проблемы с производительностью:


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

Почему этот код работает медленно, несмотря на то, что он синтаксически верен? Чтобы понять это, нам нужно углубиться в механику работы платформы 1С и СУБД (SQL Server, PostgreSQL и др.).

Механизм работы виртуальных таблиц

Виртуальные таблицы в 1С (такие как Обороты, Остатки, ОборотыДтКт и другие) — это не физические таблицы в базе данных. Это программная абстракция, которая позволяет платформе "на лету" собирать данные из реальных физических таблиц (таблицы движений и таблицы итогов).

Когда мы используем конструкцию ГДЕ, как в примере выше, алгоритм выполнения запроса выглядит следующим образом:

  1. СУБД считывает данные из таблиц итогов или движений. Поскольку параметры виртуальной таблицы не заданы, система пытается построить виртуальную таблицу за весь период существования базы данных и по всем контрагентам.
  2. Платформа формирует огромный временный набор данных, который может содержать миллионы записей.
  3. Только после того, как эта гигантская таблица сформирована в памяти или во временной базе (tempdb), к ней применяется условие ГДЕ.
  4. Система отфильтровывает лишние строки, оставляя только нужного контрагента и нужный период.

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

Правильное решение: Использование параметров виртуальной таблицы

Платформа 1С предоставляет специальный механизм для оптимизации таких запросов — параметры самой виртуальной таблицы. Это аргументы, которые передаются в скобках после имени таблицы.

Посмотрим, как выглядит оптимизированный запрос:


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

Давайте проанализируем, что изменилось с точки зрения выполнения:

  1. Платформа передает параметры периода и отбора по контрагенту непосредственно в генератор запроса SQL.
  2. СУБД при построении плана запроса видит ограничения сразу.
  3. Используются индексы таблиц итогов по периоду и измерениям (Контрагенту).
  4. Из базы данных считываются только те записи, которые соответствуют условиям.
  5. Виртуальная таблица строится сразу "чистой", без лишних данных.

Разница в скорости выполнения между первым и вторым вариантом на больших базах может достигать сотен раз (например, 10 минут против 1 секунды).

Детальный разбор параметров виртуальных таблиц

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

Регистр накопления: Обороты

Синтаксис:

РегистрНакопления.Имя.Обороты(&НачалоПериода, &КонецПериода, &Периодичность, &Условие)

Регистр сведений: СрезПоследних

Еще одно место, где часто допускают ошибки.


// ОШИБКА
ВЫБРАТЬ ... ИЗ РегистрСведений.КурсыВалют.СрезПоследних КАК Курсы
ГДЕ Курсы.Валюта = &Валюта

В данном случае система сначала получит последние курсы ВСЕХ валют на текущий момент, и только потом оставит нужную. Если в регистре тысячи валют или других объектов, это будет медленно.


// ПРАВИЛЬНО
ВЫБРАТЬ ... ИЗ РегистрСведений.КурсыВалют.СрезПоследних(, Валюта = &Валюта) КАК Курсы

Здесь система найдет последнюю запись конкретно для переданной валюты, используя индекс, что происходит мгновенно.

Нюансы работы с "Составными типами" и "Реквизитами"

Важный момент, который мы должны учитывать при оптимизации: что можно, а что нельзя передавать в параметры виртуальных таблиц?

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

Однако, если поле, по которому вы делаете отбор, не индексировано и не является измерением, иногда планировщик SQL может решить выполнить полное сканирование (Table Scan). В таких тонких случаях рекомендуется анализировать план запроса.

Влияние на RLS (Ограничение доступа на уровне записей)

Еще один аспект, о котором стоит упомянуть — это RLS (Record Level Security). Когда мы используем параметры виртуальной таблицы, система накладывает ограничения прав доступа (например, пользователю разрешено видеть продажи только по своей Организации) одновременно с получением данных.

Если же использовать ГДЕ, может возникнуть ситуация, когда:

  1. Система выбирает данные (потенциально все).
  2. Накладывает RLS (скрывая данные, которые нельзя видеть).
  3. Применяет фильтр ГДЕ.

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

Практический пример оптимизации сложного запроса

Давайте разберем более сложный пример. Допустим, нам нужно получить остатки товаров на складе "Основной", но только по группе номенклатуры "Бытовая техника".


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

В этом коде мы указали дату, что уже хорошо. Но параметр склада и номенклатуры мы оставили в секции ГДЕ. Платформа рассчитает остатки всех товаров по всем складам на указанную дату, и только потом начнет фильтровать. Если у вас 100 складов и 500 000 товаров, запрос "повесит" сервер.

Давайте исправим это:


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

Теперь запрос будет выполняться мгновенно, так как Склад и Номенклатура являются измерениями регистра, и SQL Server использует кластерный индекс для точечного получения данных.

Резюме и чек-лист для проверки запросов

Подводя итог, давайте сформируем чек-лист, который поможет вам писать быстрый и качественный код при работе с регистрами:

  1. Всегда проверяйте источник данных. Если вы видите в секции ИЗ таблицу с точкой (например, .Остатки, .СрезПоследних, .Обороты), немедленно смотрите, есть ли у нее параметры в скобках.
  2. Переносите отборы внутрь. Если в секции ГДЕ есть условия на поля, которые являются Измерениями или Периодом этого регистра, переносите их внутрь параметров виртуальной таблицы.
  3. Не забывайте про период. Для таблиц оборотов всегда указывайте начало и конец периода, даже если логика позволяет этого не делать. Отсутствие периода = сканирование всей истории.
  4. Составные условия. В параметрах виртуальных таблиц можно использовать логические операторы И, ИЛИ, массивы и списки значений (оператор В), а также иерархию (В ИЕРАРХИИ).

Соблюдение этих простых правил является стандартом разработки в среде 1С и гарантирует, что ваши отчеты будут формироваться быстро даже при значительном росте объема базы данных.

← На главную