Почему отбор в динамическом списке по индексированному полю работает медленно и как это исправить?

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

В практике разработки на платформе 1С часто встречается парадоксальная ситуация: отбор в динамическом списке по полю с индексом работает значительно медленнее, чем по неиндексированному полю или полю с составным типом данных. Особенно остро эта проблема проявляется в крупных базах данных объемом в несколько терабайт. Проанализируем причины этого явления и рассмотрим методы оптимизации.

Причина 1: Низкая кардинальность (селективность) индекса

Разберем понятие кардинальности (cardinality). Это количество уникальных значений в конкретном столбце таблицы. Если мы рассматриваем поле СостояниеОплаты, которое является перечислением и имеет всего 3–4 значения, то его кардинальность крайне низка для таблицы в несколько миллионов строк.

Проанализируем ситуацию с точки зрения SQL-сервера. Когда мы накладываем отбор по значению с низкой селективностью (например, «Оплачено»), оптимизатор SQL может ошибочно выбрать план запроса Index Seek в сочетании с Key Lookup. Если под критерий «Оплачено» попадает 70–80% записей огромной таблицы, то СУБД будет вынуждена для каждой строки из индекса обращаться к основной таблице за остальными данными. Это порождает колоссальное количество операций чтения и приводит к «зависанию» списка.

В то же время, при работе с неиндексированным полем или сложным составным типом, SQL-сервер чаще выбирает Table Scan (полное сканирование). На больших объемах последовательное чтение таблицы часто оказывается быстрее, чем миллионы разрозненных обращений через неселективный индекс.

Причина 2: Проблемы с виртуальными таблицами в соединениях

В динамических списках часто используется ЛЕВОЕ СОЕДИНЕНИЕ с виртуальными таблицами, например, РегистрСведений.ИмяРегистра.СрезПоследних. Это «некошерный» подход для высоконагруженных систем по нескольким причинам:

  1. Отсутствие проброса фильтров (Push-down): В старых версиях платформы (включая 8.3.9) механизм трансляции условий ГДЕ внутрь подзапроса виртуальной таблицы мог работать нестабильно. Вместо того чтобы получить срез последних только по нужным заказам, система сначала строит срез по всему регистру, а затем соединяет его с заказами.
  2. Parameter Sniffing: SQL-сервер кэширует план запроса при первом выполнении. Если первый раз отбор был сделан по редкому значению, план будет эффективным. Но если следующий пользователь выберет часто встречающееся значение, закэшированный план окажется катастрофически медленным.

Решение 1: Замена виртуальной таблицы физической

Рассмотрим способ оптимизации через обращение к основной таблице регистра сведений вместо использования СрезПоследних. Это позволит оптимизатору SQL лучше понять структуру данных. Попробуем переписать запрос динамического списка, используя коррелированный подзапрос или соединение с физической таблицей:


ВЫБРАТЬ
    Заказы.Ссылка,
    Статусы.СостояниеОплаты
ИЗ
    Документ.ЗаказКлиенту КАК Заказы
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СостоянияЗаказов КАК Статусы
        ПО Заказы.Ссылка = Статусы.Заказ
ГДЕ
    Статусы.Период = (
        ВЫБРАТЬ МАКСИМУМ(Т.Период)
        ИЗ РегистрСведений.СостоянияЗаказов КАК Т
        ГДЕ Т.Заказ = Заказы.Ссылка
    )

Важно: Такой подход требует наличия качественных индексов по измерениям регистра, но он работает более предсказуемо в динамических списках.

Решение 2: Денормализация данных

Для баз объемом 2 ТБ и выше самым эффективным решением является денормализация. Если вам нужно постоянно отображать «Состояние оплаты» в списке заказов и выполнять по нему быстрые отборы, лучше всего добавить соответствующий реквизит напрямую в объект Документ.ЗаказКлиенту.

Проанализируем шаги для реализации этого метода:

  1. Добавляем реквизит СостояниеОплаты в документ ЗаказКлиенту и индексируем его.
  2. В обработчике ПриЗаписи регистра сведений, который хранит эти состояния, прописываем обновление реквизита в документе.
  3. В динамическом списке убираем соединение с регистром и выводим поле напрямую из основной таблицы документа.

Это полностью исключает JOIN в запросе динамического списка, что дает максимально возможную производительность при любых отборах.

Решение 3: Настройка параметров платформы

Выясним, какие настройки динамического списка могут влиять на скорость:

Итоги

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

← На главную