При работе с большими справочниками (от 300 тысяч элементов и более) в платформе 1С:Предприятие 8.3 разработчики часто сталкиваются с критическим падением производительности динамических списков на управляемых формах. Особенно остро эта проблема проявляется в файловом режиме работы базы данных (расширение .1CD), запущенной на компьютерах с классическими жесткими дисками (HDD). Пользователи начинают жаловаться на то, что форма списка открывается по 20–30 секунд, а наложение отборов по типу «Содержит» намертво вешает систему.
В этой статье мы подробно разберем, почему стандартные механизмы динамического считывания начинают буксовать, как правильно настроить индексацию и работу СУБД, а также внедрим передовые программные методы оптимизации самого динамического списка.
Для начала проанализируем ситуацию. В нашем распоряжении плоская таблица справочника на 350 тысяч записей, содержащая более 20 реквизитов, среди которых преобладают строковые поля (ФИО, адреса, комментарии). Основные проблемы, с которыми мы сталкиваемся:
ДинамическоеСчитываниеДанных, система тратит огромное количество времени на инициализацию запроса и первичную выборку метаданных.LIKE "%Текст%". Символ процента в начале строки делает стандартные индексы типа B-Tree абсолютно бесполезными. База данных вынуждена выполнять полное сканирование таблицы (Full Table Scan), считывая терабайты секторов с медленного HDD.Ниже рассмотрим по шагам, как исправить эти архитектурные и программные проблемы.
Главная ошибка при проектировании динамического списка — выборка всех 20–22 реквизитов непосредственно в основном тексте запроса. Даже если эти поля просто выводятся в колонки, платформа вынуждена прокачивать большой объем данных. Мы должны минимизировать тело основного запроса.
Удалим из основного запроса динамического списка все второстепенные текстовые поля (такие как комментарии, подробные адреса, номера свидетельств, данные заказчиков). Оставим в запросе только ключевые поля, необходимые для идентификации и первичного быстрого поиска:
ВЫБРАТЬ
СправочникКнигаЗахоронений.Ссылка КАК Ссылка,
СправочникКнигаЗахоронений.ПометкаУдаления КАК ПометкаУдаления,
СправочникКнигаЗахоронений.Год КАК Год,
СправочникКнигаЗахоронений.Фамилия КАК Фамилия,
СправочникКнигаЗахоронений.Имя КАК Имя,
СправочникКнигаЗахоронений.Отчество КАК Отчество,
СправочникКнигаЗахоронений.Кладбище КАК Кладбище
ИЗ
Справочник.КнигаЗахоронений КАК СправочникКнигаЗахоронений
Для вывода остальных «тяжелых» полей на форму мы применим метод отложенного чтения через событие ПриПолученииДанныхНаСервере. Этот обработчик срабатывает только для тех строк таблицы, которые в данный момент реально отображаются на экране пользователя (обычно это не более 30–40 строк).
Разберем пример реализации этого механизма в модуле формы списка:
&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
// Получаем массив ссылок только для отображаемых на экране строк
МассивСсылок = Новый Массив;
Для Каждого СтрокаСписка Из Строки Цикл
МассивСсылок.Добавить(СтрокаСписка.Ключ);
КонецЦикла;
// Запрашиваем "тяжелые" данные только для этих строк
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КнигаЗахоронений.Ссылка КАК Ссылка,
| КнигаЗахоронений.Комментарий КАК Комментарий,
| КнигаЗахоронений.АдресПодачиКатафалка КАК АдресПодачиКатафалка,
| КнигаЗахоронений.НомерСвидетельства КАК НомерСвидетельства
|ИЗ
| Справочник.КнигаЗахоронений КАК КнигаЗахоронений
|ГДЕ
| КнигаЗахоронений.Ссылка В (&МассивСсылок)";
Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
Выборка = Запрос.Выполнить().Выбрать();
// Заполняем оформление строк полученными данными
Пока Выборка.Следующий() Цикл
ТекущаяСтрока = Строки[Выборка.Ссылка];
ТекущаяСтрока.Данные["Комментарий"] = Выборка.Комментарий;
ТекущаяСтрока.Данные["АдресПодачиКатафалка"] = Выборка.АдресПодачиКатафалка;
ТекущаяСтрока.Данные["НомерСвидетельства"] = Выборка.НомерСвидетельства;
КонецЦикла;
КонецПроцедуры
Благодаря этому подходу база данных мгновенно отдает первые 30 записей, а тяжелые строки с адресами и комментариями подтягиваются порциями на лету при прокрутке списка.
Проанализируем свойства самого динамического списка в редакторе формы. Если используется произвольный запрос, платформе 1С бывает тяжело самостоятельно определить уникальный ключ для каждой строки. Это приводит к тому, что система запрашивает из СУБД избыточный объем информации для позиционирования.
Выполним следующие настройки в свойствах динамического списка:
Ссылка.Попытки найти покойника по обрывкам фамилии или адресу с помощью стандартного поиска в динамическом списке неизбежно приводят к зависанию. Для решения этой проблемы мы должны использовать встроенный механизм Полнотекстового поиска данных.
Выясним, как правильно настроить этот механизм:
КнигаЗахоронений в свойстве Полнотекстовый поиск установим значение Использовать.ОбновлениеИндексаППД (раз в 5-10 минут) и СлияниеИндексаППД (один раз в сутки).Если поиск средствами динамического списка все равно работает неэффективно, мы можем спроектировать собственный программный поиск через ППД. Разберем пример реализации, когда пользователь вводит строку поиска, мы программно ищем подходящие ссылки и передаем их в отбор динамического списка:
&НаСервере
Процедура ВыполнитьПоискНаСервере(СтрокаПоиска)
СписокСсылок = Новый Массив;
// Выполняем полнотекстовый поиск по индексу
СписокППД = ПолнотекстовыйПоиск.СоздатьСписок(СтрокаПоиска, 100);
СписокППД.ПерваяЧасть();
Для Каждого ЭлементППД Из СписокППД Цикл
Если ТипЗнч(ЭлементППД.Значение) = Тип("СправочникСсылка.КнигаЗахоронений") Тогда
СписокСсылок.Добавить(ЭлементППД.Значение);
КонецЕсли;
КонецЦикла;
// Если ничего не нашли, передаем пустую ссылку, чтобы очистить список
Если СписокСсылок.Количество() = 0 Тогда
СписокСсылок.Добавить(Справочники.КнигаЗахоронений.ПустаяСсылка());
КонецЕсли;
// Устанавливаем жесткий отбор по массиву найденных ссылок
ЭлементОтбора = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке;
ЭлементОтбора.ПравоеЗначение = СписокСсылок.Использование = Истина;
КонецПроцедуры
Такой поиск по индексу ППД на 350 тысячах записей отработает за сотые доли секунды, полностью исключая Full Table Scan базы данных.
Избыточное индексирование вредит производительности. Если у вас проиндексировано 11 полей, файловая СУБД тратит слишком много ресурсов на поддержку их актуальности. Проанализируем типичные сценарии поиска пользователей и оптимизируем индексы:
Фамилия или Кладбище).По умолчанию динамический список пытается обновить данные сразу же при изменении любого символа в полях отбора. Если пользователь медленно вводит длинную фамилию, система делает 5-10 тяжелых запросов к базе данных подряд, что моментально приводит к зависанию сессии.
Разберем, как оптимизировать интерфейс ввода:
Посмотрим на пример реализации поиска с использованием обработчика ожидания:
&НаКлиенте
Процедура ПолеПоискаФамилияАвтоПодбор(Элемент, Текст, ДанныеВыбора, Параметры, Ожидание, СтандартнаяОбработка)
// Отменяем стандартный автоподбор, чтобы не перегружать базу
СтандартнаяОбработка = Ложь;
// Подключаем однократный обработчик ожидания
ПодключитьОбработчикОжидания("ВыполнитьОтложенныйПоиск", 1.5, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьОтложенныйПоиск()
// Этот код выполнится только после того, как пользователь сделает паузу в вводе
ПрименитьОтборыНаСервере();
КонецПроцедуры
Поскольку файловая база данных критически зависит от скорости дисковых операций ввода-вывода (I/O), необходимо выполнить комплекс мер по оптимизации окружения:
1Cv8.1CD и каталог с базой в список исключений вашего антивируса (Windows Defender, Kaspersky и др.). При каждом запросе динамического списка платформа активно читает файл базы данных, а антивирус пытается "на лету" сканировать эти обращения, замедляя чтение в 3-5 раз.Применение описанного комплекса мер позволяет сократить время открытия динамического списка на 350 тысячах записей с 30 секунд до мгновенного вывода (менее 1 секунды), а поиск сделать плавным и комфортным для конечных пользователей.