Разберем типичную и крайне неприятную проблему производительности в управляемом интерфейсе 1С 8.3: медленное открытие формы списка (подбора или остатков товаров) и сильную нагрузку на сервер при одновременной работе нескольких пользователей. Выясним, почему соединение с виртуальной таблицей СрезПоследних замедляет выборку в десятки раз и как правильно решить эту задачу.
Рассмотрим исходную структуру запроса динамического списка, которая приводит к зависаниям:
ВЫБРАТЬ
СправочникКлючиАналитикиУчетаНоменклатуры.Ссылка КАК АналитикаУчетаНоменклатуры,
ПРЕДСТАВЛЕНИЕ(СправочникКлючиАналитикиУчетаНоменклатуры.Номенклатура) КАК Номенклатура,
ПРЕДСТАВЛЕНИЕ(СправочникКлючиАналитикиУчетаНоменклатуры.СерияНоменклатуры) КАК СерияНоменклатуры,
ТоварыНаСкладахОстатки.КоличествоОстаток КАК Количество,
РозничныеЦеныНоменклатурыСрезПоследних.Цена КАК Цена
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ТоварыНаСкладахОстатки
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.КлючиАналитикиУчетаНоменклатуры КАК СправочникКлючиАналитикиУчетаНоменклатуры
ПО ТоварыНаСкладахОстатки.АналитикаУчетаНоменклатуры = СправочникКлючиАналитикиУчетаНоменклатуры.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РозничныеЦеныНоменклатуры.СрезПоследних(, Склад = &Склад) КАК РозничныеЦеныНоменклатурыСрезПоследних
ПО ТоварыНаСкладахОстатки.АналитикаУчетаНоменклатуры = РозничныеЦеныНоменклатурыСрезПоследних.АналитикаУчетаНоменклатуры
Проанализируем механику работы СУБД в данном случае:
СрезПоследних СУБД часто не может «пробросить» ограничение порции внутрь тяжелого вложенного подзапроса среза.Посмотрим на стандартный архитектурный подход, рекомендуемый фирмой 1С. Мы исключаем тяжелое соединение с регистром цен из текста основного запроса динамического списка, а недостающие данные получаем точечно только для тех строк, которые фактически отображаются на экране пользователя.
Разберем по шагам реализацию этого механизма:
Шаг 1. Упростим основной запрос динамического списка, оставив в нем только получение остатков и аналитики:
ВЫБРАТЬ
ТоварыНаСкладахОстатки.АналитикаУчетаНоменклатуры КАК АналитикаУчетаНоменклатуры,
ПРЕДСТАВЛЕНИЕ(ТоварыНаСкладахОстатки.АналитикаУчетаНоменклатуры.Номенклатура) КАК Номенклатура,
ПРЕДСТАВЛЕНИЕ(ТоварыНаСкладахОстатки.АналитикаУчетаНоменклатуры.СерияНоменклатуры) КАК СерияНоменклатуры,
ТоварыНаСкладахОстатки.КоличествоОстаток КАК Количество,
0 КАК Цена
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ТоварыНаСкладахОстатки
Шаг 2. В свойствах динамического списка включим обработчик события ПриПолученииДанныхНаСервере. Напишем код для получения цен только по текущей порции строк:
&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(Элемент, ОформленияСтрок)
МассивАналитик = Новый Массив;
Для Каждого Строка Из ОформленияСтрок Цикл
МассивАналитик.Добавить(Строка.Данные.АналитикаУчетаНоменклатуры);
КонецЦикла;
Если МассивАналитик.Количество() = 0 Тогда
Возврат;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Цены.АналитикаУчетаНоменклатуры КАК АналитикаУчетаНоменклатуры,
| Цены.Цена КАК Цена
|ИЗ
| РегистрСведений.РозничныеЦеныНоменклатуры.СрезПоследних(
| ,
| Склад = &Склад
| И АналитикаУчетаНоменклатуры В (&МассивАналитик)) КАК Цены";
Запрос.УстановитьПараметр("Склад", Склад);
Запрос.УстановитьПараметр("МассивАналитик", МассивАналитик);
ТаблицаЦен = Запрос.Выполнить().Выгрузить();
ТаблицаЦен.Индексы.Добавить("АналитикаУчетаНоменклатуры");
Для Каждого Строка Из ОформленияСтрок Цикл
СтрокаЦены = ТаблицаЦен.Найти(Строка.Данные.АналитикаУчетаНоменклатуры, "АналитикаУчетаНоменклатуры");
Если СтрокаЦены <> Неопределено Тогда
Строка.Ячейки.Цена.Значение = СтрокаЦены.Цена;
Строка.Ячейки.Цена.Текст = Формат(СтрокаЦены.Цена, "ЧЦ=15; ЧДЦ=2");
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Важное ограничение: При таком способе пользователи не смогут сортировать список по колонке Цена или накладывать на нее отбор через стандартную панель динамического списка, так как эти данные заполняются после выполнения основного SQL-запроса.
Если используется платформа 1С:Предприятие 8.3.8 и выше (с соответствующим режимом совместимости), в динамических списках поддерживается работа с пакетными запросами и временными таблицами. Это позволяет отфильтровать цены на уровне СУБД через промежуточную выборку.
Посмотрим на пример построения такого запроса:
ВЫБРАТЬ
ТоварыНаСкладахОстатки.АналитикаУчетаНоменклатуры КАК АналитикаУчетаНоменклатуры,
ТоварыНаСкладахОстатки.КоличествоОстаток КАК Количество
ПОМЕСТИТЬ ВТОстатки
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ТоварыНаСкладахОстатки
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТОстатки.АналитикаУчетаНоменклатуры КАК АналитикаУчетаНоменклатуры,
ПРЕДСТАВЛЕНИЕ(СправочникКлючи.Номенклатура) КАК Номенклатура,
ПРЕДСТАВЛЕНИЕ(СправочникКлючи.СерияНоменклатуры) КАК СерияНоменклатуры,
ВТОстатки.Количество КАК Количество,
ЕСТЬNULL(РозничныеЦеныНоменклатурыСрезПоследних.Цена, 0) КАК Цена
ИЗ
ВТОстатки КАК ВТОстатки
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.КлючиАналитикиУчетаНоменклатуры КАК СправочникКлючи
ПО ВТОстатки.АналитикаУчетаНоменклатуры = СправочникКлючи.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РозничныеЦеныНоменклатуры.СрезПоследних(
,
Склад = &Склад
И АналитикаУчетаНоменклатуры В
(ВЫБРАТЬ
ВТОстатки.АналитикаУчетаНоменклатуры
ИЗ
ВТОстатки КАК ВТОстатки)) КАК РозничныеЦеныНоменклатурыСрезПоследних
ПО ВТОстатки.АналитикаУчетаНоменклатуры = РозничныеЦеныНоменклатурыСрезПоследних.АналитикаУчетаНоменклатуры
Обратите внимание: мы передали условие АналитикаУчетаНоменклатуры В (ВЫБРАТЬ ...) непосредственно в параметры виртуальной таблицы СрезПоследних. Это принудительно ограничивает выборку среза только теми товарами, по которым действительно есть ненулевые остатки на складе.
Если структура конфигурации доступна для изменения, проанализируем параметры регистра сведений:
АналитикаУчетаНоменклатуры стоит на первом месте в составе измерений регистра сведений РозничныеЦеныНоменклатуры. Это сформирует кластерный индекс вида АналитикаУчетаНоменклатуры + Период, что позволит СУБД выполнять быстрый поиск (Index Seek) вместо полного сканирования таблицы.MAX(Период) на лету.