В практике разработки на платформе 1С:Предприятие одной из самых распространенных причин низкой производительности отчетов и обработок является неправильное написание запросов к виртуальным таблицам. Часто разработчики, особенно на начальном этапе, сталкиваются с ситуацией, когда простой, казалось бы, запрос к регистру накопления или бухгалтерии выполняется непозволительно долго.
Разберем эту ситуацию подробно, проанализируем причины замедления и рассмотрим правильные методы оптимизации, опираясь на обсуждение проблемы и технические стандарты платформы.
Рассмотрим типичный сценарий. Нам необходимо получить обороты продаж по конкретному контрагенту за определенный период. Интуитивно разработчик может написать запрос, используя конструкцию ГДЕ (WHERE), воспринимая виртуальную таблицу как обычную таблицу базы данных.
Давайте посмотрим на пример кода, который часто вызывает проблемы с производительностью:
// НЕПРАВИЛЬНЫЙ ПОДХОД
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПродажиОбороты.Номенклатура,
| ПродажиОбороты.КоличествоОборот,
| ПродажиОбороты.СуммаОборот
|ИЗ
| РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты
|ГДЕ
| ПродажиОбороты.Период МЕЖДУ &НачДата И &КонДата
| И ПродажиОбороты.Контрагент = &Контрагент";
Почему этот код работает медленно, несмотря на то, что он синтаксически верен? Чтобы понять это, нам нужно углубиться в механику работы платформы 1С и СУБД (SQL Server, PostgreSQL и др.).
Виртуальные таблицы в 1С (такие как Обороты, Остатки, ОборотыДтКт и другие) — это не физические таблицы в базе данных. Это программная абстракция, которая позволяет платформе "на лету" собирать данные из реальных физических таблиц (таблицы движений и таблицы итогов).
Когда мы используем конструкцию ГДЕ, как в примере выше, алгоритм выполнения запроса выглядит следующим образом:
ГДЕ.Получается, что мы заставляем сервер делать колоссальную лишнюю работу: читать миллионы записей, чтобы потом выбросить 99% из них. Это создает высокую нагрузку на дисковую подсистему и процессор, а также может приводить к блокировкам таблиц.
Платформа 1С предоставляет специальный механизм для оптимизации таких запросов — параметры самой виртуальной таблицы. Это аргументы, которые передаются в скобках после имени таблицы.
Посмотрим, как выглядит оптимизированный запрос:
// ПРАВИЛЬНЫЙ ПОДХОД
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПродажиОбороты.Номенклатура,
| ПродажиОбороты.КоличествоОборот,
| ПродажиОбороты.СуммаОборот
|ИЗ
| РегистрНакопления.Продажи.Обороты(
| &НачДата,
| &КонДата,
| ,
| Контрагент = &Контрагент
| ) КАК ПродажиОбороты";
Давайте проанализируем, что изменилось с точки зрения выполнения:
Разница в скорости выполнения между первым и вторым вариантом на больших базах может достигать сотен раз (например, 10 минут против 1 секунды).
Давайте рассмотрим, какие параметры мы можем и должны использовать, чтобы наш код был максимально эффективным. У разных типов регистров набор параметров отличается, но логика остается единой.
Синтаксис:
РегистрНакопления.Имя.Обороты(&НачалоПериода, &КонецПериода, &Периодичность, &Условие)
Авто, Месяц, День).Еще одно место, где часто допускают ошибки.
// ОШИБКА
ВЫБРАТЬ ... ИЗ РегистрСведений.КурсыВалют.СрезПоследних КАК Курсы
ГДЕ Курсы.Валюта = &Валюта
В данном случае система сначала получит последние курсы ВСЕХ валют на текущий момент, и только потом оставит нужную. Если в регистре тысячи валют или других объектов, это будет медленно.
// ПРАВИЛЬНО
ВЫБРАТЬ ... ИЗ РегистрСведений.КурсыВалют.СрезПоследних(, Валюта = &Валюта) КАК Курсы
Здесь система найдет последнюю запись конкретно для переданной валюты, используя индекс, что происходит мгновенно.
Важный момент, который мы должны учитывать при оптимизации: что можно, а что нельзя передавать в параметры виртуальных таблиц?
В параметр &Условие виртуальной таблицы эффективно передавать отборы по Измерениям. Именно измерения индексируются в таблицах итогов. Если вы попытаетесь наложить отбор по Реквизиту (который не является измерением), прирост производительности может быть меньше, так как платформе все равно придется читать записи для проверки реквизита, хотя это все же лучше, чем использование ГДЕ.
Однако, если поле, по которому вы делаете отбор, не индексировано и не является измерением, иногда планировщик SQL может решить выполнить полное сканирование (Table Scan). В таких тонких случаях рекомендуется анализировать план запроса.
Еще один аспект, о котором стоит упомянуть — это RLS (Record Level Security). Когда мы используем параметры виртуальной таблицы, система накладывает ограничения прав доступа (например, пользователю разрешено видеть продажи только по своей Организации) одновременно с получением данных.
Если же использовать ГДЕ, может возникнуть ситуация, когда:
ГДЕ.Использование параметров позволяет оптимизатору запросов объединить условия отбора и условия RLS, что значительно ускоряет выборку на базах со сложными правами доступа.
Давайте разберем более сложный пример. Допустим, нам нужно получить остатки товаров на складе "Основной", но только по группе номенклатуры "Бытовая техника".
// НЕОПТИМАЛЬНО
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура,
| ТоварыНаСкладахОстатки.ВНаличииОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, ) КАК ТоварыНаСкладахОстатки
|ГДЕ
| ТоварыНаСкладахОстатки.Склад = &Склад
| И ТоварыНаСкладахОстатки.Номенклатура В ИЕРАРХИИ (&ГруппаТоваров)";
В этом коде мы указали дату, что уже хорошо. Но параметр склада и номенклатуры мы оставили в секции ГДЕ. Платформа рассчитает остатки всех товаров по всем складам на указанную дату, и только потом начнет фильтровать. Если у вас 100 складов и 500 000 товаров, запрос "повесит" сервер.
Давайте исправим это:
// ОПТИМАЛЬНО
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура,
| ТоварыНаСкладахОстатки.ВНаличииОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(
| &Дата,
| Склад = &Склад И Номенклатура В ИЕРАРХИИ (&ГруппаТоваров)
| ) КАК ТоварыНаСкладахОстатки";
Теперь запрос будет выполняться мгновенно, так как Склад и Номенклатура являются измерениями регистра, и SQL Server использует кластерный индекс для точечного получения данных.
Подводя итог, давайте сформируем чек-лист, который поможет вам писать быстрый и качественный код при работе с регистрами:
ИЗ таблицу с точкой (например, .Остатки, .СрезПоследних, .Обороты), немедленно смотрите, есть ли у нее параметры в скобках.ГДЕ есть условия на поля, которые являются Измерениями или Периодом этого регистра, переносите их внутрь параметров виртуальной таблицы.И, ИЛИ, массивы и списки значений (оператор В), а также иерархию (В ИЕРАРХИИ).Соблюдение этих простых правил является стандартом разработки в среде 1С и гарантирует, что ваши отчеты будут формироваться быстро даже при значительном росте объема базы данных.