Среди разработчиков 1С долгое время ведется дискуссия о том, имеет ли значение порядок следования измерений при проектировании регистров накопления и сведений. Некоторые считают это пережитком «эпохи 1С:Предприятия 7.7», другие же утверждают, что неверное расположение полей способно замедлить работу системы в разы. Давайте детально разберем эту тему, проанализируем поведение современных систем управления базами данных (СУБД), таких как MS SQL Server и PostgreSQL, и выясним, как порядок измерений влияет на производительность запросов и скорость записи.
Для понимания сути проблемы нам необходимо обратиться к архитектуре баз данных. Главное правило разработчика БД — попасть в индекс. Платформа 1С:Предприятие автоматически создает индексы для таблиц СУБД на основе структуры метаданных, заданной в конфигураторе. В случае регистров накопления наиболее критичным является кластерный индекс таблицы итогов.
Кластерный индекс физически определяет порядок хранения строк в таблице на диске. Для таблицы итогов регистра остатков платформа формирует этот индекс по следующей структуре:
Период + Измерение_1 + Измерение_2 + ... + Измерение_N
Составной индекс работает по правилу левого префикса. Это означает, что СУБД может использовать этот индекс для быстрого поиска (операция Index Seek) только в том случае, если в условиях запроса («ГДЕ» или параметры виртуальной таблицы) указаны первые поля индекса. Если мы пропускаем первое поле составного индекса, но накладываем условие на второе или третье, СУБД не сможет выполнить эффективный поиск по дереву индекса и будет вынуждена прибегнуть к сканированию всей таблицы (операция Index Scan), что крайне негативно сказывается на производительности.
В методических материалах прошлых лет часто встречалось утверждение: «На первое место в регистре нужно ставить наиболее селективные поля» (то есть поля с наибольшим количеством уникальных значений, например, Номенклатура, а не Организация). Современная практика работы с СУБД опровергает этот подход как универсальное правило.
Если в запросе накладываются условия равенства на все поля составного индекса, СУБД абсолютно все равно, в каком порядке эти поля расположены в индексе. Скорость поиска будет одинаковой, поскольку высота B-Tree дерева и количество считываемых страниц данных не изменятся. Например, если в запросе жестко ограничены и Организация, и Склад, и Номенклатура, порядок их объявления в метаданных регистра не повлияет на скорость выполнения конкретно этого запроса.
Порядок полей важен не из-за самой селективности, а из-за частоты совместного или раздельного использования полей в реальных запросах системы. И именно здесь кроется причина, почему в типовых конфигурациях 1С первым измерением всегда идет наименее селективное поле — Организация.
В любой типовой конфигурации 1С (ERP, КА, УТ, БП) в регистрах остатков первым измерением практически всегда выступает Организация, затем идет Склад, и только потом Номенклатура. Это кажется нелогичным с точки зрения селективности (организаций в базе обычно мало, а номенклатуры — сотни тысяч), однако этот подход является максимально оптимальным по двум причинам:
Номенклатура, то при запросе остатков по организации в целом СУБД не смогла бы использовать индекс и уходила бы в тяжелый Index Scan.Давайте рассмотрим ситуацию, когда разработчик решает проиндексировать отдельное измерение регистра, установив у него свойство «Индексировать» в конфигураторе. Например, у нас есть регистр с измерениями: Номенклатура (1-е измерение), Характеристика (2-е), Склад (3-е, индексированное).
Если мы пишем запрос на получение остатков только по конкретному складу, мы ожидаем, что СУБД использует созданный индекс по измерению Склад. Однако на практике оптимизатор запросов СУБД часто игнорирует этот индекс. Разберем по шагам, почему так происходит:
Склад не содержит данных о самих остатках (ресурсах регистра). Он содержит только значение склада и ссылку на кластерный индекс.Clustered Index Scan).Таким образом, наличие индекса не гарантирует его использование, если структура основного кластерного индекса (порядок измерений регистра) спроектирована неоптимально.
При добавлении новых измерений в регистр важно помнить о «ширине» кластерного ключа. Кластерный индекс физически удерживает структуру всей таблицы. Все дополнительные некластерные индексы (создаваемые платформой при индексировании измерений) хранят внутри себя ключи кластерного индекса в качестве указателей на строки.
Если у вас в регистре создано слишком много измерений (например, 8-10 детальных аналитик), то:
В современных СУБД (например, в PostgreSQL начиная с версии 15) реализована оптимизация Index Skip Scan (сканирование с пропуском). Она позволяет использовать составной индекс, даже если первое поле индекса пропущено в условиях запроса. Например, когда мы ищем остатки конкретного товара без указания организации.
Однако этот механизм работает эффективно только тогда, когда пропущенное первое поле имеет очень мало уникальных значений (низкую кардинальность), как в случае с организацией. Если бы на первом месте стояла высокоселективная номенклатура (миллионы уникальных записей), технология Skip Scan физически не смогла бы отработать оптимально, и СУБД гарантированно ушла бы в полное сканирование таблицы.
Проанализируем конкретный пример. Предположим, у нас есть простая задача — получить остатки по складу. Рассмотрим два варианта проектирования регистра.
Вариант 1 (Неоптимальный):
Порядок измерений: Номенклатура -> Склад.
При попытке получить остатки по конкретному складу СУБД не сможет использовать левый префикс кластерного индекса таблицы итогов (так как первым полем идет номенклатура, которая в запросе не указана). Запрос вида:
ВЫБРАТЬ
ТоварыОстатки.Номенклатура,
ТоварыОстатки.Склад,
ТоварыОстатки.ВНаличииОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Склад = &Склад) КАК ТоварыОстатки
приведет к полному сканированию таблицы итогов (Index Scan), что при миллионной базе вызовет колоссальные задержки.
Вариант 2 (Оптимальный):
Порядок измерений: Склад -> Номенклатура.
В данном случае СУБД мгновенно выполнит операцию точечного поиска (Index Seek) по первому измерению Склад, ограничив выборку только нужными записями. Производительность такого запроса будет максимальной.
Для того чтобы ваши регистры работали максимально быстро как на чтение, так и на запись, придерживайтесь следующих правил при определении порядка измерений в конфигураторе:
Организация, НаправлениеДеятельности).