Почему SQL Server выполняет Clustered Index Seek при поиске по неприоритетным измерениям регистра?

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

В практике разработчика 1С часто возникает ситуация, когда теория из учебников по оптимизации (например, «Настольной книги эксперта») вступает в кажущееся противоречие с реальностью. Мы привыкли считать, что если в запросе не указано первое измерение индекса, СУБД обязана выполнить полное сканирование (Index Scan). Однако при анализе планов запросов в SQL Server Profiler для регистров накопления мы часто видим Clustered Index Seek, даже если условие наложено только на последнее измерение. Давайте подробно разберем, почему так происходит, как работает оптимизатор SQL Server 2005/2008 и что на самом деле скрывается за операцией Seek в контексте итогов 1С.

Механизм формирования таблиц итогов в 1С

Для начала проанализируем структуру таблицы итогов регистра накопления (обычно это таблица с префиксом _AccumRgT). Платформа 1С:Предприятие при создании регистра автоматически формирует кластерный индекс. Рассмотрим типичный состав полей этого индекса:

  1. _Period — Период (дата итогов, обычно начало месяца или 5999 год для актуальных остатков).
  2. _Fld... — Измерение 1 (например, Номенклатура).
  3. _Fld... — Измерение 2 (например, Характеристика).
  4. _Fld... — Измерение 3 (например, Склад).

Важно понимать, что поле _Period всегда стоит на первом месте в кластерном индексе таблиц итогов. Когда мы пишем запрос к виртуальной таблице Остатки, мы часто не указываем период, подразумевая получение текущих остатков. Платформа 1С при трансляции этого запроса в T-SQL неявно добавляет условие на максимальную дату (01.11.5999). Рассмотрим пример такого запроса:


ВЫБРАТЬ
    Остатки.Номенклатура КАК Номенклатура,
    Остатки.Склад КАК Склад,
    Остатки.ВНаличииОстаток КАК ВНаличииОстаток
ИЗ
    РегистрНакопления.СвободныеОстатки.Остатки(, Склад = &Склад) КАК Остатки

Несмотря на то, что Склад является третьим измерением в структуре, SQL Server может показать в плане Clustered Index Seek. Разберемся, почему это происходит.

Разбор трансляции запроса в T-SQL

Проанализируем, во что превращается этот запрос на уровне СУБД. Платформа генерирует код, подобный следующему:


SELECT
    T2._Fld63162RRef,
    T2._Fld63164RRef,
    SUM(T2._Fld63165)
FROM dbo._AccumRgT63169 T2
WHERE T2._Period = '5999-11-01 00:00:00'
  AND T2._Fld63164RRef = @P1
GROUP BY T2._Fld63162RRef, T2._Fld63164RRef

Здесь кроется ключевой ответ: в условии WHERE присутствует поле _Period. Для SQL Server это поле является префиксом (самым левым элементом) кластерного индекса. Поскольку значение периода жестко задано (константа 5999 год), оптимизатор понимает, что ему не нужно сканировать всю таблицу. Он может выполнить «поиск» (Seek) по первой колонке индекса, чтобы сразу перейти к блоку данных, относящемуся к текущим остаткам.

Seek vs Scan: что происходит на самом деле?

Выясним причину, по которой это не является «идеальным» поиском. В SQL Server 2005 оператор Index Seek может означать две разные вещи:

  1. Singleton Lookups: Поиск конкретной записи по всем полям индекса (самый быстрый).
  2. Index Range Scan: Поиск диапазона внутри индекса.

В нашем случае происходит именно поиск диапазона. SQL Server делает «прыжок» (Seek) к первой записи, где период равен 5999, а затем начинает последовательно перебирать (сканировать) все записи этого периода, проверяя условие по Складу. В плане запроса это все равно будет называться Clustered Index Seek, но фактически внутри найденного периода выполняется сканирование. Это так называемый «частичный поиск» или поиск с остаточным предикатом (Residual Predicate).

Проанализируем ситуацию подробнее: если в базе данных всего один период итогов (что типично для небольших баз), то такой Seek по факту прочитает столько же данных, сколько и полный Scan таблицы. Однако, если бы в таблице итогов хранились данные за 10 лет помесячно, Seek по периоду 5999 позволил бы мгновенно отсечь 99% ненужных данных, что является огромным преимуществом.

Влияние статистики и селективности

Рассмотрим, почему план может измениться. Оптимизатор принимает решение на основе статистики. Если статистика говорит СУБД, что записей с конкретным складом очень мало, а записей с периодом 5999 очень много, он может попытаться использовать другие индексы (если бы они были). Но так как 1С не создает некластерные индексы для таблиц итогов по умолчанию, у SQL Server нет выбора, кроме как использовать кластерный индекс.

Важные моменты для анализа производительности:

Практические рекомендации по оптимизации

Чтобы запросы к регистрам выполнялись максимально эффективно, проанализируем следующие шаги:

  1. Соблюдайте порядок измерений: В конфигураторе 1С измерения должны располагаться в порядке от наиболее часто используемых в фильтрах к наименее используемым. Это напрямую влияет на структуру SQL-индекса.
  2. Используйте индексирование измерений: В свойствах измерения регистра накопления можно установить флаг Индексировать. Это создаст дополнительный некластерный индекс для основной таблицы движений, но не для таблицы итогов. Помните об этом различии!
  3. Обновляйте статистику: Для SQL Server 2005 крайне важно регулярное выполнение UPDATE STATISTICS. Устаревшая статистика может заставить оптимизатор выбрать Scan там, где Seek был бы эффективнее, или наоборот.
  4. Анализируйте план запроса: Всегда обращайте внимание не только на тип операции (Seek/Scan), но и на Argument в деталях плана. Там указано, какие условия используются для поиска (Seek Keys), а какие — для фильтрации уже после прочтения (Residual Predicate).

Подводя итог, можно сказать, что Clustered Index Seek в 1С при запросе остатков — это зачастую «заслуга» неявного фильтра по периоду. Это гораздо лучше, чем полное сканирование всей таблицы, но не освобождает разработчика от необходимости правильно проектировать структуру измерений для минимизации остаточного сканирования внутри периода.

← На главную