Одной из наиболее распространенных проблем производительности в интерфейсе обычных форм 1С является медленная работа справочника Номенклатура при попытке вывести в него остатки товаров в реальном времени. Основная причина кроется в механике работы события ПриПолученииДанных. Это событие вызывается каждый раз, когда пользователь прокручивает список, открывает форму или меняет размер окна. Если внутри этого события выполняется «тяжелый» запрос к базе данных, интерфейс начинает заметно «тормозить».
Рассмотрим пошагово, как проанализировать ситуацию и применить различные стратегии оптимизации, чтобы сделать работу пользователя комфортной.
Проанализируем типичный код, который часто встречается в конфигурациях. Проблема заключается в том, что запрос выполняется для каждой «пачки» строк, которые отрисовываются на экране. Если в коде присутствуют методы НайтиПоКоду или запросы к виртуальным таблицам регистра бухгалтерии (РегистрБухгалтерии.Хозрасчетный.Остатки), то нагрузка на SQL-сервер возрастает многократно. Регистр бухгалтерии архитектурно сложнее регистров накопления, так как он предназначен для итогов по счетам с учетом субконто, что делает получение остатков по нему более ресурсозатратным.
Самый эффективный способ убрать задержки при скроллинге — это отказаться от выполнения запросов в событии ПриПолученииДанных. Вместо этого мы можем подготовить данные один раз при открытии формы или по требованию.
Рассмотрим алгоритм реализации кэширования:
тзОстатки.ПриПолученииДанных будем просто искать значения в этой таблице.Пример реализации в модуле формы:
Перем тзОстатки;
Процедура ОбновитьКэшОстатков()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХозрасчетныйОстатки.Субконто1 КАК Товар,
| ХозрасчетныйОстатки.КоличествоОстатокДт КАК Остаток
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, Счет = &Счет, , ) КАК ХозрасчетныйОстатки";
Запрос.УстановитьПараметр("Период", ТекущаяДата());
Запрос.УстановитьПараметр("Счет", ПланыСчетов.Хозрасчетный.Товары);
тзОстатки = Запрос.Выполнить().Выгрузить();
тзОстатки.Индексы.Добавить("Товар");
КонецПроцедуры
Процедура СправочникСписокПриПолученииДанных(Элемент, ОформленияСтрок)
Для Каждого Оформление Из ОформленияСтрок Цикл
СсылкаНаТовар = Оформление.ДанныеСтроки.Ссылка;
СтрокаОстатка = тзОстатки.Найти(СсылкаНаТовар, "Товар");
Если СтрокаОстатка <> Неопределено Тогда
Оформление.Ячейки.Остаток.Значение = СтрокаОстатка.Остаток;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Важный нюанс: Обязательно используйте индексацию таблицы значений по полю Товар. Без индекса метод Найти будет работать линейно, что на больших объемах данных снова приведет к замедлению.
Если специфика бизнеса требует получения остатков «в моменте» (например, при очень интенсивных продажах) и кэширование не подходит, нужно максимально оптимизировать сам запрос. Разберем основные ошибки в исходном коде автора темы.
Во-первых, использование НайтиПоКоду внутри процедуры — это критическая ошибка. Каждый такой вызов — это отдельный мини-запрос к БД. Выясним, как сделать правильно: ссылки на склады нужно получить заранее (например, в ПриОткрытии) и передавать в запрос уже готовые объекты или массив.
Во-вторых, обратите внимание на условия в виртуальной таблице. Чем точнее наложен фильтр внутри параметров виртуальной таблицы (в скобках), тем быстрее SQL-сервер вернет результат.
Оптимизированный вариант запроса в ПриПолученииДанных:
// В параметрах виртуальной таблицы передаем только те товары, которые сейчас на экране
МассивНоменклатуры = Новый Массив;
Для Каждого Строка Из ОформленияСтрок Цикл
МассивНоменклатуры.Добавить(Строка.ДанныеСтроки.Ссылка);
КонецЦикла;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Субконто1 КАК Товар,
| СУММА(ВЫБОР КОГДА Остатки.Субконто2 = &Склад1 ТОГДА Остатки.КоличествоОстатокДт ИНАЧЕ 0 КОНЕЦ) КАК Склад1_Остаток
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Остатки(&Дата, Счет В (&Счета), , Субконто1 В (&МассивНоменклатуры)) КАК Остатки
|СГРУППИРОВАТЬ ПО
| Остатки.Субконто1";
Проанализируем структуру регистров. Если в вашей конфигурации ведется параллельный оперативный учет (например, как в УТ 10.3 или УПП), крайне рекомендуется забирать остатки из регистров накопления (ТоварыНаСкладах, ТоварыВРознице). Виртуальные таблицы остатков регистров накопления работают значительно быстрее, чем аналогичные таблицы регистра бухгалтерии, так как они оптимизированы именно под складской учет и не тянут за собой лишнюю информацию о проводках и планах счетов.
Несмотря на то что ваше основное приложение может работать на обычных формах, платформа 8.3 позволяет интегрировать элементы управляемого интерфейса. Мы можем создать форму списка номенклатуры на Управляемых формах и использовать механизм Динамического списка.
Преимущества этого подхода:
Для реализации этого метода достаточно в свойствах конфигурации разрешить использование управляемых форм в обычном приложении и создать новую форму списка. Вызов такой формы из обычного интерфейса происходит стандартным методом ОткрытьФорму().
Для продвинутых систем можно реализовать асинхронное обновление. Рассмотрим схему:
ПодключитьОбработчикОжидания, который раз в несколько секунд проверяет готовность данных.Этот метод позволяет пользователю начать работу с формой мгновенно, а актуальные остатки «подтянутся» через секунду-две без фризов интерфейса.
Выясним еще одну причину тормозов: база TempDB. При выполнении запросов с использованием временных таблиц (а именно так 1С работает с виртуальными таблицами остатков при передаче массива ссылок) активно задействуется системная база SQL-сервера TempDB. Если она расположена на медленном HDD, никакая оптимизация кода не поможет на 100%. Убедитесь, что TempDB находится на быстром SSD-диске, а количество ее файлов соответствует количеству ядер процессора.
Подведем итог: для быстрого решения проблемы используйте индексированную таблицу значений в памяти формы. Для стратегического решения — переходите на управляемые формы и динамические списки, либо оптимизируйте структуру регистров, перенося складской учет на регистры накопления.