В практике разработчика 1С часто возникает ситуация, когда теория из учебников по оптимизации (например, «Настольной книги эксперта») вступает в кажущееся противоречие с реальностью. Мы привыкли считать, что если в запросе не указано первое измерение индекса, СУБД обязана выполнить полное сканирование (Index Scan). Однако при анализе планов запросов в SQL Server Profiler для регистров накопления мы часто видим Clustered Index Seek, даже если условие наложено только на последнее измерение. Давайте подробно разберем, почему так происходит, как работает оптимизатор SQL Server 2005/2008 и что на самом деле скрывается за операцией Seek в контексте итогов 1С.
Для начала проанализируем структуру таблицы итогов регистра накопления (обычно это таблица с префиксом _AccumRgT). Платформа 1С:Предприятие при создании регистра автоматически формирует кластерный индекс. Рассмотрим типичный состав полей этого индекса:
_Period — Период (дата итогов, обычно начало месяца или 5999 год для актуальных остатков)._Fld... — Измерение 1 (например, Номенклатура)._Fld... — Измерение 2 (например, Характеристика)._Fld... — Измерение 3 (например, Склад).Важно понимать, что поле _Period всегда стоит на первом месте в кластерном индексе таблиц итогов. Когда мы пишем запрос к виртуальной таблице Остатки, мы часто не указываем период, подразумевая получение текущих остатков. Платформа 1С при трансляции этого запроса в T-SQL неявно добавляет условие на максимальную дату (01.11.5999). Рассмотрим пример такого запроса:
ВЫБРАТЬ
Остатки.Номенклатура КАК Номенклатура,
Остатки.Склад КАК Склад,
Остатки.ВНаличииОстаток КАК ВНаличииОстаток
ИЗ
РегистрНакопления.СвободныеОстатки.Остатки(, Склад = &Склад) КАК Остатки
Несмотря на то, что Склад является третьим измерением в структуре, SQL Server может показать в плане Clustered Index Seek. Разберемся, почему это происходит.
Проанализируем, во что превращается этот запрос на уровне СУБД. Платформа генерирует код, подобный следующему:
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) по первой колонке индекса, чтобы сразу перейти к блоку данных, относящемуся к текущим остаткам.
Выясним причину, по которой это не является «идеальным» поиском. В SQL Server 2005 оператор Index Seek может означать две разные вещи:
В нашем случае происходит именно поиск диапазона. SQL Server делает «прыжок» (Seek) к первой записи, где период равен 5999, а затем начинает последовательно перебирать (сканировать) все записи этого периода, проверяя условие по Складу. В плане запроса это все равно будет называться Clustered Index Seek, но фактически внутри найденного периода выполняется сканирование. Это так называемый «частичный поиск» или поиск с остаточным предикатом (Residual Predicate).
Проанализируем ситуацию подробнее: если в базе данных всего один период итогов (что типично для небольших баз), то такой Seek по факту прочитает столько же данных, сколько и полный Scan таблицы. Однако, если бы в таблице итогов хранились данные за 10 лет помесячно, Seek по периоду 5999 позволил бы мгновенно отсечь 99% ненужных данных, что является огромным преимуществом.
Рассмотрим, почему план может измениться. Оптимизатор принимает решение на основе статистики. Если статистика говорит СУБД, что записей с конкретным складом очень мало, а записей с периодом 5999 очень много, он может попытаться использовать другие индексы (если бы они были). Но так как 1С не создает некластерные индексы для таблиц итогов по умолчанию, у SQL Server нет выбора, кроме как использовать кластерный индекс.
Важные моменты для анализа производительности:
Чтобы запросы к регистрам выполнялись максимально эффективно, проанализируем следующие шаги:
Индексировать. Это создаст дополнительный некластерный индекс для основной таблицы движений, но не для таблицы итогов. Помните об этом различии!UPDATE STATISTICS. Устаревшая статистика может заставить оптимизатор выбрать Scan там, где Seek был бы эффективнее, или наоборот.Подводя итог, можно сказать, что Clustered Index Seek в 1С при запросе остатков — это зачастую «заслуга» неявного фильтра по периоду. Это гораздо лучше, чем полное сканирование всей таблицы, но не освобождает разработчика от необходимости правильно проектировать структуру измерений для минимизации остаточного сканирования внутри периода.