При работе с динамическими списками в 1С:Предприятии 8.3 мы часто сталкиваемся с необходимостью программно устанавливать отборы, чтобы пользователи могли быстро находить нужные данные. Однако, в некоторых случаях, особенно при поиске по полям, которые могут содержать данные разных типов (например, числа, но отображаемые как строки), или при использовании определенных видов сравнения, производительность отбора может существенно снижаться. Давайте разберем одну из таких ситуаций и выясним, как ее эффективно решить, а также рассмотрим общие подходы к оптимизации отборов в динамических списках.
Представим типовую ситуацию, когда нам необходимо установить программный отбор в динамическом списке по полю, которое может быть числовым, но для поиска мы преобразуем его в строку и ищем по вхождению подстроки. Автор задачи столкнулся с проблемой, когда программный отбор, реализованный следующим образом, работал крайне медленно, а иногда и вовсе не находил записи, которые очевидно присутствовали в списке, особенно если они располагались далеко от начала выборки:
Список.Отбор.Элементы.Очистить();
Если ЗначениеЗаполнено(НомерПартии) Тогда
ЭлементОтбора = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НомерПартии");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Содержит;
ЧтоИскать = СтрЗаменить(НомерПартии, " ", Символы.НПП);
ЭлементОтбора.ПравоеЗначение = ЧтоИскать;
ЭлементОтбора.Использование = Истина;
КонецЕсли;
Наблюдения автора были критически важны для понимания сути проблемы. Он отметил, что если данных в списке много (например, за несколько месяцев), то поиск фактически ограничивается последним месяцем или небольшим количеством первых строк. Записи за более ранние периоды, даже если они присутствовали в общей выборке списка, не находились или поиск занимал несколько минут или даже часов. Это навело на мысль, что проблема не в отсутствии данных, а в крайней неэффективности механизма поиска.
Такое поведение (быстрый поиск в начале и медленный или отсутствующий далее) очень характерно для ситуаций, когда система компоновки данных (СКД) не может эффективно использовать индексы базы данных. Метод сравнения ВидСравненияКомпоновкиДанных.Содержит (аналогичный оператору SQL LIKE '%значение%') по своей природе является одним из самых ресурсоемких, так как требует полного сканирования данных. Если при этом поле НомерПартии в базе данных является числовым, а мы ищем по его строковому представлению, да еще и с заменой символов (СтрЗаменить(НомерПартии, " ", Символы.НПП)), это дополнительно усложняет задачу для СУБД и СКД.
Чтобы глубже понять корни проблемы, давайте разберем несколько ключевых факторов, влияющих на производительность отбора в нашем случае:
Использование ВидСравненияКомпоновкиДанных.Содержит: Этот вид сравнения, как уже упоминалось, чрезвычайно требователен к ресурсам. Он ищет подстроку в поле, что в большинстве случаев не позволяет СУБД использовать индексы по этому полю. СУБД вынуждена выполнять полное сканирование таблицы, строка за строкой, что на больших объемах данных приводит к катастрофическому падению производительности.
Поиск по строковому представлению числового поля: Поле НомерПартии, вероятнее всего, имеет числовой тип в базе данных. Однако в коде мы преобразуем его в строку (ЧтоИскать = СтрЗаменить(НомерПартии, " ", Символы.НПП)) и затем используем для сравнения. Когда мы сравниваем строковое значение с числовым полем в базе данных, система компоновки данных (а затем и СУБД) должна выполнить неявное или явное преобразование типов. Если это преобразование происходит в SQL-запросе к полю базы данных, то СУБД не сможет использовать существующие индексы по этому полю, даже если они были созданы.
Обработка СтрЗаменить: Использование функции СтрЗаменить для модификации искомого значения перед сравнением также добавляет накладные расходы и, что еще важнее, делает практически невозможным использование любых индексов по полю НомерПартии. СУБД пришлось бы сначала применить СтрЗаменить к каждой записи в поле НомерПартии, а затем выполнить поиск подстроки, что абсолютно неэффективно.
Динамическая выборка данных: Динамический список по умолчанию выбирает данные порциями. Если поиск занимает много времени, то при прокрутке списка или попытке найти записи, находящиеся в глубоких порциях, возникают задержки, которые пользователь воспринимает как "зависание".
Из анализа становится ясно, что наш первоначальный подход создавал "идеальные" условия для низкой производительности: мы использовали медленный вид сравнения, не могли задействовать индексы из-за преобразования типов и модификации искомого значения, и все это усугублялось при работе с большими объемами данных.
Автор задачи нашел элегантное и эффективное решение, которое позволяет значительно ускорить отбор. Основная идея заключается в том, чтобы использовать ВидСравненияКомпоновкиДанных.ВСписке и передавать в него массив, содержащий как строковое, так и числовое представление искомого значения. Это позволяет СКД и СУБД более гибко обрабатывать сравнение и, в некоторых случаях, использовать индексы.
Давайте подробно рассмотрим модифицированный код отбора:
ЭлементОтбора = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НомерПартии");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке;
ЧтоИскать = СтрЗаменить(НомерПартии, " ", Символы.НПП);
ИлиИскать = Число(ЧтоИскать); // Пытаемся преобразовать в число
СписокЗнач = Новый Массив;
СписокЗнач.Добавить(ЧтоИскать); // Добавляем строковое представление
СписокЗнач.Добавить(ИлиИскать); // Добавляем числовое представление
ЭлементОтбора.ПравоеЗначение = СписокЗнач;
ЭлементОтбора.Использование = Истина;
Разберем это решение по шагам:
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке;: Вместо поиска подстроки (Содержит) мы теперь ищем точное совпадение с одним из значений в предоставленном списке. Это существенно более производительный вид сравнения, который СУБД может оптимизировать с помощью индексов (оператор SQL IN (...)).
ЧтоИскать = СтрЗаменить(НомерПартии, " ", Символы.НПП);: Мы по-прежнему подготавливаем строковое представление искомого значения, удаляя пробелы. Это может быть важно, если в номерах партий пробелы игнорируются при вводе, но могут присутствовать в базе данных или быть частью визуального представления.
ИлиИскать = Число(ЧтоИскать);: Это критически важный шаг. Мы пытаемся преобразовать подготовленную строку в число. Если НомерПартии является числом, то это преобразование пройдет успешно. Если НомерПартии может быть, например, буквенно-цифровым кодом, то Число() вернет 0 или вызовет ошибку, в зависимости от контекста и версии платформы, но в данном случае оно сработает для числовых частей. Главное, что мы получаем числовое представление нашего искомого значения.
СписокЗнач = Новый Массив; СписокЗнач.Добавить(ЧтоИскать); СписокЗнач.Добавить(ИлиИскать);: Мы создаем массив и добавляем в него ОБА представления искомого значения: как строковое, так и числовое. Таким образом, мы говорим СКД: "Ищи записи, где поле НомерПартии равно либо этому строковому значению, либо этому числовому значению".
ЭлементОтбора.ПравоеЗначение = СписокЗнач;: Передаем массив в качестве правого значения для сравнения.
Почему это работает эффективнее? Система компоновки данных и СУБД получают возможность искать точное совпадение по полю НомерПартии с любым из значений в массиве. Если поле НомерПартии в базе данных является числовым, то СКД будет сравнивать его с числовым значением из массива, что позволяет задействовать индексы. Если же поле НомерПартии может содержать строковые значения, то будет произведено сравнение со строковым значением. СКД достаточно умна, чтобы корректно сформировать SQL-запрос, используя оператор IN, который сможет эффективно работать с индексами для числовых столбцов, а для строковых — также выполнить более прямой поиск, чем LIKE '%...'.
Таким образом, мы даем системе максимальные шансы на использование оптимизированных механизмов поиска, учитывая возможную неоднородность данных в поле НомерПартии или его представление.
Помимо точечного решения проблемы со смешанными типами данных и видом сравнения, существует ряд общих рекомендаций, которые помогают значительно улучшить производительность динамических списков и отборов в 1С. Давайте рассмотрим их подробно:
Индексирование полей:
Одним из самых мощных инструментов для ускорения поиска и отбора является правильное индексирование полей в базе данных. Поля, по которым мы чаще всего устанавливаем отборы или выполняем сортировку, обязательно должны быть проиндексированы. Для платформы 1С это означает, что в конфигураторе для реквизита объекта (например, справочника или документа) следует установить свойство "Индексировать" в значение "Истина".
Если же мы имеем дело со сложным запросом динамического списка, где важна не только скорость отбора, но и порядок следования элементов, соответствующий основному упорядочиванию для данного объекта, то можем использовать свойство "Индексировать с доп. упорядочиванием". Это свойство, доступное для реквизитов, создает дополнительный индекс, который включает в себя поля основного упорядочивания объекта, что обеспечивает более эффективный просмотр больших объемов информации с отбором по значению данного реквизита и с сохранением необходимого порядка.
Важно: Индексы хорошо работают для точных совпадений (Равно), сравнений (Больше, Меньше), поиска по началу строки (НачинаетсяС), и как мы выяснили, для поиска ВСписке. Для поиска подстроки (Содержит) индексы почти никогда не используются эффективно.
Порядок отборов:
Если на динамический список накладывается несколько отборов, их порядок может влиять на производительность. Рекомендуем следующую последовательность:
Равно, ВСписке). Эти отборы максимально быстро сузят выборку данных.Содержит, если его совсем нельзя избежать). Работа с такими полями на уже максимально отфильтрованном наборе данных будет наименее затратной.Оптимизация запроса СКД:
Запрос, лежащий в основе динамического списка, является фундаментом его производительности. Необходимо стремиться к максимальной простоте и эффективности:
ВЫРАЗИТЬ для больших строк: Функция ВЫРАЗИТЬ для преобразования больших строк (например, неограниченной длины) в запросе динамического списка может сильно замедлять его работу, так как приводит к созданию временных таблиц и полной материализации данных.ЛЕВОЕ СОЕДИНЕНИЕ) увеличивает сложность и время выполнения запроса.ИЛИ, НЕ, ПОДСТРОКА, МЕСЯЦ, ВЫБОР КОГДА: Эти операторы и функции в условиях запроса часто мешают СУБД использовать индексы. Замените их, если возможно, на более прямые и индексируемые условия. Например, вместо Поле = Значение1 ИЛИ Поле = Значение2 используйте Поле В (Значение1, Значение2).Флаг "Динамическое считывание данных":
В свойствах запроса динамического списка (для "Произвольного" запроса с основной таблицей) существует флаг "Динамическое считывание данных". Этот флаг влияет на размер порций данных, которые забираются из базы данных на клиент. Если флаг установлен, данные считываются маленькими порциями (например, по 45 строк). Это может приводить к постоянным обращениям к серверу при прокрутке и вызывать подвисания, особенно если запрос сам по себе тяжелый.
Если флаг не установлен, порции данных больше (например, по 1000 строк). Это делает прокрутку более комфортной, так как меньше обращений к серверу, но увеличивает время выполнения запроса для первой выборки. Рекомендуется использовать этот флаг, если запрос сам по себе тяжелый, но в выборке обычно ожидается немного записей. В противном случае, для больших списков с частой прокруткой, лучше его не устанавливать, чтобы получать данные большими блоками.
Ограничение полей отбора:
Для предотвращения "зависания" списков со сложными запросами или большим количеством данных, начиная с версии платформы 8.3.10, разработчики могут использовать метод УстановитьОграниченияИспользованияВОтборе(). Этот метод позволяет программно запрещать пользователям устанавливать отборы по определенным полям, которые могут приводить к неоптимальным запросам или слишком медленной работе. Это полезно для защиты от неопытных пользователей, которые могут неосознанно "положить" систему, применив отбор по очень медленному полю.
Особенности работы с ВидСравненияКомпоновкиДанных.Содержит (повторение):
Подчеркнем еще раз: использование ВидСравненияКомпоновкиДанных.Содержит (поиск подстроки) на больших объемах данных практически всегда приводит к очень медленной работе и может вызывать "зависание" системы. Это происходит потому, что такой поиск, как правило, не может эффективно использовать индексы базы данных, требуя полного сканирования таблицы. Если вам абсолютно необходимо искать по подстроке, рассмотрите возможность создания полнотекстового индекса или использования специализированных механизмов поиска, которые работают быстрее, чем стандартный LIKE '%...%'.
Программная установка сложных отборов:
При программной установке отбора мы можем создавать не только простые элементы, но и группы элементов отбора. Это позволяет формировать сложные условия с использованием логических операторов "И", "ИЛИ", "НЕ", что дает большую гибкость. Например, можно создать группу "ИЛИ", в которую поместить условие Поле = ЧисловоеЗначение и Поле = СтроковоеЗначение, что аналогично нашему решению с ВСписке, но более явно.
Дополнительные общие рекомендации по СУБД и интерфейсу:
Номенклатура.ВидНоменклатуры вместо ВидНоменклатуры, если ВидНоменклатуры уже выбрано как поле. Каждое такое обращение через точку в запросе может привести к дополнительному соединению или подзапросу, что снижает производительность.ПриПолученииДанныхНаСервере(): Если запрос СКД не справляется с отображением произвольных данных или требуется очень сложное оформление, когда стандартных средств СКД недостаточно, можно использовать событие ПриПолученииДанныхНаСервере() динамического списка. В этом событии вы можете вручную модифицировать данные, полученные из СКД. Однако, стоит помнить, что это может привести к дополнительным нагрузкам и усложнению логики, поэтому используйте его только при крайней необходимости.Таким образом, мы видим, что решение проблемы медленного отбора в динамическом списке требует комплексного подхода. От точечной оптимизации кода отбора с учетом типов данных до общих принципов построения запросов и обслуживания базы данных – каждый шаг вносит свой вклад в общую производительность системы. Применяя эти рекомендации, мы можем значительно улучшить пользовательский опыт и обеспечить стабильную работу наших приложений на платформе 1С:Предприятие.