Как максимально быстро получить остатки по 15 складам в форме списка номенклатуры?

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

При работе в конфигурации «Управление торговлей 10.3» (на платформе 1С 8.2 с обычными формами) часто возникает задача вывести текущие остатки товаров в разрезе нескольких складов прямо в форму списка справочника Номенклатура. Однако стандартный подход «запрос в цикле» приводит к катастрофическому падению производительности: форма начинает «замирать» на 5–10 секунд при каждом скроллинге. Разберем детально, как решить эту проблему профессионально.

Выясняем причину тормозов: почему нельзя использовать ПриВыводеСтроки

Основная ошибка начинающих разработчиков — выполнение запроса в обработчике события ПриВыводеСтроки. Давайте проанализируем ситуацию: это событие вызывается отдельно для каждой строки, которая отрисовывается на экране. Если у пользователя открыто окно, в котором помещается 20–30 строк номенклатуры, система выполнит 20–30 отдельных запросов к базе данных. С учетом того, что требуется получить данные по 15 складам, нагрузка на СУБД возрастает экспоненциально. Каждый вызов — это сетевой пакет до сервера и обратно, что и создает те самые «фризы».

Решение №1: Использование события ПриПолученииДанных

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

Рассмотрим пошагово алгоритм реализации этого метода:

  1. Извлекаем ссылки на всю отображаемую номенклатуру из параметра ОформленияСтрок.
  2. Формируем массив этих ссылок.
  3. Выполняем один запрос к виртуальной таблице РегистрНакопления.ТоварыНаСкладах.Остатки с фильтром по нашему массиву номенклатуры и списку нужных складов.
  4. Выгружаем результат запроса в таблицу или соответствие.
  5. Распределяем полученные значения по строкам оформления.

Посмотрим на пример кода, который реализует эту логику:


Процедура СправочникСписокПриПолученииДанных(Элемент, ОформленияСтрок)
    
    // 1. Собираем номенклатуру из видимых строк
    МассивНом = Новый Массив;
    Для Каждого СтрокаОформления Из ОформленияСтрок Цикл
        МассивНом.Добавить(СтрокаОформления.ДанныеСтроки.Ссылка);
    КонецЦикла;
    
    // 2. Получаем список складов (например, из настроек пользователя)
    СписокСкладов = ПолучитьСписокНужныхСкладов(); 

    // 3. Выполняем один запрос для всех строк
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |	ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
    |	ТоварыНаСкладахОстатки.Склад КАК Склад,
    |	ТоварыНаСкладахОстатки.КоличествоОстаток КАК Количество
    |ИЗ
    |	РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура В (&МассивНом) И Склад В (&СписокСкладов)) КАК ТоварыНаСкладахОстатки";
    
    Запрос.УстановитьПараметр("Период", ТекущаяДата());
    Запрос.УстановитьПараметр("МассивНом", МассивНом);
    Запрос.УстановитьПараметр("СписокСкладов", СписокСкладов);
    
    ТЗ_Остатки = Запрос.Выполнить().Выгрузить();
    
    // 4. Заполняем колонки в ОформленияСтрок
    Для Каждого СтрокаОформления Из ОформленияСтрок Цикл
        СсылкаНом = СтрокаОформления.ДанныеСтроки.Ссылка;
        
        // Для каждого склада ищем остаток в ТЗ_Остатки
        Для Каждого Склад Из СписокСкладов Цикл
            ПараметрыОтбора = Новый Структура("Номенклатура, Склад", СсылкаНом, Склад);
            НайденныеСтроки = ТЗ_Остатки.НайтиСтроки(ПараметрыОтбора);
            
            Если НайденныеСтроки.Количество() > 0 Тогда
                СтрокаОформления.Ячейки["Склад_" + Склад.Наименование].УстановитьТекст(НайденныеСтроки[0].Количество);
            КонецЕсли;
        КонецЦикла;
    КонецЦикла;
    
КонецПроцедуры

Решение №2: Кэширование данных на клиенте

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

Создадим переменную модуля формы мКэшОстатков типа Соответствие. При получении данных мы будем проверять, есть ли остаток для конкретной номенклатуры в кэше. Если есть — берем оттуда, если нет — добавляем ссылку в массив для запроса к БД. После запроса дополняем кэш новыми данными. Это позволит системе «запоминать» остатки тех позиций, которые пользователь уже видел, и не запрашивать их повторно до обновления формы.

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

Решение №3: Оптимизация на уровне SQL

Разберем, как сделать сам запрос максимально быстрым. Главное правило при работе с виртуальными таблицами 1С: всегда используйте параметры виртуальной таблицы.

Проанализируем две конструкции:

  1. ГДЕ Номенклатура В (&Массив) — здесь платформа может сначала получить ВСЕ остатки по всем товарам, а потом отсечь лишние на стороне сервера приложений.
  2. Остатки(&Момент, Номенклатура В (&Массив)) — здесь фильтр передается напрямую в SQL-запрос, и база данных возвращает только нужные записи.

В случае с 15 складами и большой номенклатурой разница в скорости может достигать 10 и более раз.

Решение №4: Отложенное обновление через обработчик ожидания

Если даже оптимизированный запрос занимает ощутимое время, можно применить «визуальную хитрость». При прокрутке списка мы не вызываем запрос сразу. Вместо этого мы запускаем ПодключитьОбработчикОжидания с задержкой 0.5 секунды. Если пользователь продолжает крутить список, обработчик переподключается. Как только пользователь остановился, срабатывает процедура получения остатков только для тех строк, которые остались на экране. Это делает интерфейс максимально отзывчивым, так как тяжелые вычисления не блокируют скроллинг.

Архитектурный подход: специализированный регистр сведений

Если в базе миллионы записей и стандартные регистры накопления работают медленно из-за высокой фрагментации итогов, рассмотрим вариант создания вспомогательного непериодического РегистрСведений.АктуальныеОстатки. В него можно записывать агрегированные данные (Товар, Склад, Количество) в момент проведения документов. Чтение из плоского регистра сведений по индексу всегда быстрее, чем расчет остатков через виртуальные таблицы, особенно если вам не нужна точность «до секунды» с учетом всех резервов, а достаточно оперативного остатка на складе.

Резюмируя: основной секрет скорости в УТ 10 — это уход от построчной обработки в ПриВыводеСтроки к пакетной обработке в ПриПолученииДанных с обязательным использованием параметров виртуальных таблиц и кэшированием результатов в памяти формы.

← На главную