Почему динамический список в 1С работает медленнее, чем в консоли запросов, и как его оптимизировать?

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

Каждый разработчик в среде «1С:Предприятие» рано или поздно сталкивается с парадоксальной ситуацией: тяжелый многострочный запрос в консоли запросов выполняется практически мгновенно (за 0.8–1.2 секунды), но стоит перенести его в динамический список (ДС) формы — и обновление списка начинает занимать 3–5 секунд. При этом интерфейс начинает откровенно зависать при скроллинге. Для динамического списка время выполнения запроса более 0.1 секунды — это уже критический показатель, при котором комфортная работа пользователя становится невозможной. Давайте детально разберем физику этого процесса, выясним причины такого поведения платформы и рассмотрим пошаговые сценарии оптимизации динамического списка.

1. Как платформа трансформирует исходный запрос

Проанализируем ситуацию, которая происходит «под капотом» системы при выводе данных в динамический список. В консоли запросов ваш код выполняется в первозданном виде — СУБД получает именно тот текст запроса, который вы написали. С динамическим списком всё обстоит иначе. Платформа выполняет колоссальную работу по адаптации запроса под текущее состояние формы:

  1. Оборачивание во внешний подзапрос: Платформа берет ваш исходный текст запроса и помещает его внутрь конструкции SELECT TOP... СУБД. Это необходимо для обеспечения порционной выборки данных (обычно по 45 строк).
  2. Динамическое добавление отборов: Все отборы, установленные пользователем на форме или разработчиком в настройках компоновщика данных, внедряются платформой непосредственно в итоговый SQL-запрос в виде условий WHERE.
  3. Динамическая сортировка: Направление сортировки, выбранное пользователем кликом по колонке, транслируется в секцию ORDER BY внешнего запроса.

Из-за подобного «оборачивания» СУБД часто строит совершенно иной, неоптимальный план выполнения. Запрос, который в консоли «пролетал» быстро благодаря простому сканированию индекса, в динамическом списке может превратиться в тяжелое сканирование всей таблицы (Table Scan) или привести к неоптимальному соединению таблиц (Hash Match вместо Nested Loops).

2. Разыменование полей составного типа («Через точку»)

Рассмотрим подробнее одну из самых неочевидных и частых причин падения производительности динамических списков — обращение к реквизитам ссылочных полей составного типа через точку. Например, у вас есть реквизит ДокументОснование, который может принимать значения документов РеализацияТоваровУслуг, ОтчетОРозничныхПродажах или ПередачаТоваров. Если в запросе динамического списка написать:


ВЫБРАТЬ
    ОсновнаяТаблица.Ссылка,
    ОсновнаяТаблица.ДокументОснование.Дата КАК ДатаОснования
ИЗ
    Документ.ВозвратТоваровОтПокупателя КАК ОсновнаяТаблица

Платформа при трансляции этого кода в SQL будет вынуждена выполнить неявные левые соединения (LEFT JOIN) со всеми таблицами, которые входят в этот составной тип данных. Если таких типов в составе реквизита десятки, простой запрос превратится в монстра со множеством соединений. Ситуация усугубляется, если на эти связанные таблицы наложены ограничения прав на уровне записей (RLS) — в этом случае условия RLS добавятся к каждому неявному соединению.

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


ВЫБРАТЬ
    ОсновнаяТаблица.Ссылка,
    ВЫРАЗИТЬ(ОсновнаяТаблица.ДокументОснование КАК Документ.РеализацияТоваровУслуг).Дата КАК ДатаОснования
ИЗ
    Документ.ВозвратТоваровОтПокупателя КАК ОсновнаяТаблица

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

3. Настройка свойства «Основная таблица» и динамическое считывание

Выясним причину влияния свойства ОсновнаяТаблица динамического списка на общую производительность. Работа списка напрямую зависит от того, заполнено ли это свойство в настройках ДС:

4. Использование временных таблиц в запросах ДС

Использование временных таблиц (ВТ) внутри текста запроса динамического списка — это критическая архитектурная ошибка. Разберем по шагам, почему это происходит. Поскольку динамический список запрашивает данные порциями при каждом движении пользователя по списку, все временные таблицы будут заново создаваться, заполняться и уничтожаться при каждом скролле или интерактивном обновлении списка. Это создает колоссальную непроизводительную нагрузку на базу данных tempdb сервера СУБД и быстро приводит к деградации производительности всей системы.

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

5. Оптимальное получение данных через обработчик «ПриПолученииДанныхНаСервере»

Часто в динамических списках пытаются соединить основную физическую таблицу (например, справочник номенклатуры) с виртуальными таблицами остатков или цен (например, РегистрНакопления.ТоварыНаСкладах.Остатки). Такое соединение «убивает» производительность списка, так как СУБД приходится рассчитывать итоги по всей базе данных еще до того, как применится порционная выборка.

Проанализируем правильный подход: перенос получения сложных расчетных данных в событие таблицы динамического списка ПриПолученииДанныхНаСервере(). В этом обработчике платформа передает коллекцию строк, которые *уже отобраны* для вывода на экран (обычно это всего 30–40 строк). Мы можем написать один легкий, точечный запрос, который получит данные только для этих конкретных ссылок.

Рассмотрим пример реализации такого подхода:


&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
    
    // Шаг 1. Соберем ключи (ссылки номенклатуры) отображаемых на экране строк
    МассивСсылок = Новый Массив;
    Для Каждого СтрокаСписок Из Строки Цикл
        МассивСсылок.Добавить(СтрокаСписок.Ключ);
    КонецЦикла;
    
    Если МассивСсылок.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Шаг 2. Выполним быстрый запрос к виртуальной таблице только по нашему массиву
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
        |	ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Остаток
        |ИЗ
        |	РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (&МассивСсылок)) КАК ТоварыНаСкладахОстатки";
    
    Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
    ТаблицаОстатков = Запрос.Выполнить().Выгрузить();
    ТаблицаОстатков.Индексировать("Номенклатура");
    
    // Шаг 3. Заполним оформление строк полученными данными
    Для Каждого СтрокаСписок Из Строки Цикл
        ДанныеСтроки = СтрокаСписок.Значение;
        НайденнаяСтрока = ТаблицаОстатков.Найти(СтрокаСписок.Ключ, "Номенклатура");
        Если НайденнаяСтрока <> Неопределено Тогда
            // "Остаток" — это добавленная произвольная колонка в динамическом списке
            ДанныеСтроки.Данные.Остаток = НайденнаяСтрока.Остаток;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Этот прием позволяет снизить нагрузку на сервер СУБД в тысячи раз, обеспечивая мгновенный отклик интерфейса при скроллинге списка любой сложности.

6. Неявное влияние скрытых полей

Еще один важный момент: если в запросе динамического списка объявлено поле (особенно ссылочное), но на самой форме у этого поля снята галочка видимости, платформа все равно может запрашивать эти данные из БД на уровне SQL. Если такое скрытое поле дополнительно тянет за собой разыменование других реквизитов, производительность списка упадет без видимых причин.

Решение проблемы: Очищайте схему запроса динамического списка от неиспользуемых полей. Не оставляйте «про запас» поля, которые реально не выводятся на форму и не используются в группировках или отборах.

7. Индексация для отборов и сортировок

Если пользователь пытается выполнить сортировку или установить отбор по полю, которое получено в результате соединения таблиц и не имеет индекса в базе данных СУБД, механизм динамического считывания перестает работать эффективно. СУБД физически не сможет выдать первые 45 строк по порядку без полного предварительного сканирования и сортировки в памяти всей таблицы из миллионов записей.

Решение проблемы: Все поля, по которым пользователю разрешено выполнять интерактивные отборы и сортировки в динамическом списке, обязательно должны быть проиндексированы. Если сортировка требуется по сложному вычисляемому или соединенному полю, то такие данные лучше заранее денормализовать — рассчитывать при записи документов/справочников и хранить в физических реквизитах или регистре сведений с включенным индексированием.

← На главную