Селективный индекс от 1С - что выберет MS SQL?

18.10.22

База данных - HighLoad оптимизация

Большие объемы данных вынуждают глубже смотреть в логику работы оптимизатора СУБД. Ваше понимание о селективном индексе может расходится с логикой оптимизатора SQL. Это еще больше привязывает конкретную систему к конкретной СУБД. Построение хорошего запроса к СУБД попрежнему остается искусством, а искусственный интеллект пока еще туда не добрался

Скачать файл

ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.

Наименование Бесплатно
План стандартный индекс
.txt 10,85Kb
6
6 Скачать бесплатно
План запроса - оптимизатор выбрал нестандартный индекс
.txt 10,56Kb
5
5 Скачать бесплатно

Кто он – селективный индекс.

В предыдущей статье Партицированная дисциплина программиста был показан пример запроса на соединение двух таблиц для регистра сведений, и показано как MS SQL выбирает потоки данных для merge join с использованием стандартных  индексов 1С.  В частности было отмечено, что без дополнительных условий в Index seek, в поток для Megre join попадают все записи индекса и приходится указывать дополнительные фильтры для ограничения. Вопрос: почему так происходит? - остался открытым.

Все что описано ниже, это мои выводы на основании анализа поведения оптимизатора MS SQL 2019  в разных условиях. Официально изложенных алгоритмов, которые использует оптимизатор MS SQL я не нашел, если знаете ссылку пишите в комментариях.

Сначала вспомним понятие селективного индекса

Если сказать кратко – индекс для данного запроса является селективным, если при его использовании можно выбрать

  • Больше уникальных строк
  • С меньшим количеством дублей
  • Наименьшее количество строк на каждую комбинацию ключевых значений

Про селективность хорошо написано тут (правда для Oracle, но это же общая концепция) Что такое селективный индекс

 

Возьмем оптимизированный запрос из предыдущей статьи

ВЫБРАТЬ РАЗЛИЧНЫЕ
    СУУ_АгрегированныеДенежныеТранзакции.СвязаннаяОпИдИсхСистемы КАК СвязаннаяОпИдИсхСистемы
ПОМЕСТИТЬ Врем_ИдОперацийИзТранзакций
ИЗ
    РегистрСведений.СУУ_АгрегированныеДенежныеТранзакции КАК СУУ_АгрегированныеДенежныеТранзакции
ГДЕ
    СУУ_АгрегированныеДенежныеТранзакции.Период >= &ДатаНачала

ИНДЕКСИРОВАТЬ ПО
    СвязаннаяОпИдИсхСистемы
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    СУУ_АгрегированнаяСделкаКП.Период,
    СУУ_АгрегированнаяСделкаКП.ИсходнаяСистема,
    СУУ_АгрегированнаяСделкаКП.ИдИсхСистемы КАК ИдИсхСистемы,
    СУУ_АгрегированнаяСделкаКП.ОсновнойСчет,
    СУУ_АгрегированнаяСделкаКП.НогаСделки
ПОМЕСТИТЬ РезультатВыбранныеВерсииСделок
ИЗ
    РегистрСведений.СУУ_АгрегированнаяСделкаКП КАК СУУ_АгрегированнаяСделкаКП
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Врем_ИдОперацийИзТранзакций КАК Врем_ИдОперацийИзТранзакций
        ПО СУУ_АгрегированнаяСделкаКП.ИдИсхСистемы = Врем_ИдОперацийИзТранзакций.СвязаннаяОпИдИсхСистемы
ГДЕ
    СУУ_АгрегированнаяСделкаКП.Период >= ДОБАВИТЬКДАТЕ(&ДатаНачала, МЕСЯЦ, -3)

И опять его запустим со стандартными индексами 1С. MS SQL нас интересует последний запрос


INSERT INTO #tt3 WITH(TABLOCK) (_Q_001_F_000, _Q_001_F_001RRef, _Q_001_F_002, _Q_001_F_003RRef, _Q_001_F_004RRef) SELECT

T1._Period,

T1._Fld18861RRef,

T1._Fld18865,

T1._Fld18863RRef,

T1._Fld19363RRef

FROM dbo._InfoRg18860 T1 WITH(NOLOCK)

INNER JOIN #tt2 T2 WITH(NOLOCK)

ON (T1._Fld18865 = T2._Q_000_F_000)

WHERE ((T1._Fld628 = @P1)) AND ((T1._Period >= @P2))',N'@P1 numeric(10),@P2 datetime2(3)

Смотрим общую цену, которую зафиксировал оптимизатор -  в попугаях 7767

План получается с Index Seek по типовому индексу _InfoR18860_ByDims18897_STRRRR

 

Структура индекса

 

План запроса ниже, видно, что основная тяжесть ввода вывода идет на Index Seek и операции Merge

 

 

Вроде все хорошо, по правилам и предсказуемо, но давайте добавим ему другой индекс в котором, убрано поле _Fld628 . Это поле содержит 0 поскольку в типовой конфигурации есть, но не используются

 

Смотрим результат. Неожиданно – SQL сервер выбрал новый индекс сам , даже при том что _Fld628 (разделителя) там вообще нет! Хотя есть индекс _InfoR18860_ByDims18897_STRRRR который формально удовлетворяет всем условиям.

 

План при этом получился лучше, но ненамного

 

Разница

  1. По стандартному индексу идет  |--Index Seek(OBJECT:([MIS_PROD2].[dbo].[_InfoRg18860].[_InfoR18860_ByDims18897_STRRRR] AS [T1]), SEEK:([T1].[_Fld628]=[@P1]),  WHERE:([MIS_PROD2].[dbo].[_InfoRg18860].[_Period] as [T1].[_Period]>=[@P2]) ORDERED FORWARD)
  2. По нестандартному индексу идет скан с проверкой всех условий |--Index Scan(OBJECT:([MIS_PROD2].[dbo].[_InfoRg18860].[_InfoR18860_MySuperWithout_Fld628_ByDims18897_STRRRR] AS [T1]),  WHERE:([MIS_PROD2].[dbo].[_InfoRg18860].[_Fld628] as [T1].[_Fld628]=[@P1] AND [MIS_PROD2].[dbo].[_InfoRg18860].[_Period] as [T1].[_Period]>=[@P2]) ORDERED FORWARD)

Мы выбираем селективный индекс, а оптимизатор выбирает …

Почему MS SQL так сделал?

Сначала я подумал так «Скорее всего поле, где _Fld628 = 0 в каждой записи, убивает всю селективность индекса и как только появляется достойная альтернатива, то MS SQL  сам бежит к ней»

Но после обсуждения на Хабре и нескольких экспериментов выяснилось, что дело в количестве IO по индексу

Т.е. по индексу  _InfoR18860_ByDims18897_STRRRR  фактическая статистика показала почти 5 млн чтений

 

 

а по индексу _InfoR18860_MySuperWithoutFld628_ByDims18897_STRRRR около 3 млн чтений, причем через Index Scan. Индекс _InfoR18860_MySuperWithoutFld628_ByDims18897_STRRRR явно не укладывается в критерии селективности, но оптимизатор MS SQL видит, что по нему работать быстрее

 

 

Фрагментация у стандартного индекса действительно была, и после Rebuild оптимизатор опять начал его выбирать как основной! Более того – посмотрите как уменьшилось количество чтений до 3.4 миллиона

 

 

 

Вывод: Селективность для оптимизатора MS SQL это уже не основной показатель и как показано ниже для других СУБД тоже. Оптимизатор MS SQL может отказаться использовать селективный индекс даже при сильной фрагментации. Более того, в статье MS SQL Server: ваши статистики не работают! Так ли все плохо на самом деле? видно, что поле разделителя влияет на качество статистик MS SQL

 

Вопрос А в какое место индекса, тогда поставить это поле _Fld628 (ОбластьДанныхОсновныеДанные) , которое 1С по умолчанию ставит в начало (префикс) всех индексов?

Это сложный вопрос. Если ОбластьДанныхОсновныеДанные используется хотя бы с несколькими значениями, селективность повысится в стандартном индексе, учитывая условия на равенства которые добавляет 1С  T1._Fld628 = @P1 (равенство всегда в приоритете у оптимизатора нежели >= <=)

Но все очень зависит от СУБД , например тут описаны мифы о селективных индексах причем с планами для разных СУБД

Most selective first ?

“The myth is extraordinarily persistent in the SQL Server environment and appears even in the official documentation. The reason is that SQL Server keeps a histogram for the first index column only. But that means that the recommendation should read like “uneven distributed columns first” because histograms are not very useful for evenly distributed columns anyway.”

Т.е. первая колонка в индексе решает все и ее количество уникальных значений. Если там один 0 работа с остальными полями идет уже менее эффективно. Если 0 1 2, то это тоже сильно ситуацию не исправляет, поскольку для Merge без доп условий пойдет поток данных по всему T1._Fld628 = @P1

В целом лучше жить без поля ОбластьДанныхОсновныеДанные, чем с ним, но если без него нельзя то улучшить ситуацию можно только альтернативным построением запроса самой платформы, а это уже другая   история в следующей статье.  Буду рад видеть Вас на нашем телеграм-канале  😊

MS SQL Оптимизация индексы селективность highload

См. также

SALE! %

Инструментарий разработчика Роли и права Запросы СКД Программист Руководитель проекта Платформа 1С v8.3 Управляемые формы Запросы Система компоновки данных Платные (руб)

Набор инструментов программиста и специалиста 1С для всех конфигураций на управляемых формах. В состав входят инструменты: Консоль запросов, Консоль СКД, Консоль кода, Редактор объекта, Анализ прав доступа, Метаданные, Поиск ссылок, Сравнение объектов, Все функции, Подписки на события и др. Редактор запросов и кода с раскраской и контекстной подсказкой. Доработанный конструктор запросов тонкого клиента. Продукт хорошо оптимизирован и обладает самым широким функционалом среди всех инструментов, представленных на рынке.

12000 10000 руб.

02.09.2020    166432    924    403    

899

Инструментарий разработчика Чистка данных Свертка базы Инструменты администратора БД Системный администратор Программист Руководитель проекта Платформа 1С v8.3 Россия Платные (руб)

Инструмент представляет собой обработку для проведения свёртки или обрезки баз данных. Работает на ЛЮБЫХ конфигурациях (УТ, БП, ERP и т.д.). Поддерживаются управляемые и обычные формы. Может выполнять свертку сразу нескольких баз данных и выполнять их автоматически без непосредственного участия пользователя. Решение в Реестре отечественного ПО

8400 руб.

20.08.2024    10294    86    36    

93

DevOps для 1С DevOps и автоматизация разработки Программист Стажер Платные (руб)

Данный онлайн-курс (интенсив) предусматривает изучение процессов, инструментов и методик DevOps, их применение при разработке на платформе 1С. 

2500 руб.

20.06.2023    23286    21    4    

320

Инструментарий разработчика Программист Платформа 1С v8.3 Конфигурации 1cv8 Платные (руб)

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

9360 руб.

17.05.2024    25580    88    48    

132

Пакетная печать Печатные формы Инструментарий разработчика Программист Платформа 1С v8.3 Запросы 1С:Зарплата и кадры бюджетного учреждения 1С:ERP Управление предприятием 2 1С:Управление торговлей 11 Платные (руб)

Инструмент, позволяющий абсолютно по-новому взглянуть на процесс разработки печатных форм. Благодаря конструктору можно значительно снизить затраты времени на разработку печатных форм, повысить качество и "прозрачность" разработки, а также навести порядок в многообразии корпоративных печатных форм.

22200 руб.

06.10.2023    16410    40    9    

75

Мобильная разработка Мессенджеры и боты Платформа 1С v8.3 Платные (руб)

Теперь создать telegram-бота - элементарно. Достаточно просто нарисовать блок-схему телеграм-бота, и он сразу заработает. Это возможно при использовании Графического конструктора телеграм-ботов. Это единственный конструктор ботов для telegram, чье качество и функционал подтверждены фирмой 1С, есть сертификат 1С:Совместимо. Расширение в интерактивном режиме, с помощью блок-схем, позволяет с минимальными трудозатратами создать телеграм-ботов в любой конфигурации, работающей на платформе «1С:Предприятие 8.3».

13200 руб.

27.12.2021    39066    109    163    

203

SALE! %

Инструменты администратора БД Инструментарий разработчика Роли и права Программист Платформа 1С v8.3 Конфигурации 1cv8 Россия Платные (руб)

Расширение позволяет без изменения кода конфигурации выполнять проверки при вводе данных, скрывать от пользователя недоступные ему данные, выполнять код в обработчиках. Не изменяет данные конфигурации, легко устанавливается практически на любую конфигурацию на управляемых формах.

15000 10000 руб.

10.11.2023    11151    40    27    

66

SALE! %

Инструментарий разработчика Инструменты администратора БД Системный администратор Программист Платформа 1С v8.3 Управляемые формы Конфигурации 1cv8 Россия Платные (руб)

Универсальный инструмент программиста для администрирования конфигураций. Сборник наиболее часто используемых обработок под единым интерфейсом.

4800 3840 руб.

14.01.2013    189760    1146    0    

916
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. redfred 18.10.22 19:45 Сейчас в теме
_Fld628 в included columns ушёл? Можете приложить актуальные планы в xml формате?
2. 1CUnlimited 323 18.10.22 20:08 Сейчас в теме
Никаких included columns нет, В индексе _InfoR18860_MySuperWithoutFld628_ByDims18897_STRRRR поля _Fld628 нет

>Можете приложить актуальные планы в xml формате?
Я в текстовом приложил в конце статьи они хорошо открываются моноширийным шрифтом в 1С . Вам чтото там не хватает ?
XML мне нужно заново гонять запрос.
3. redfred 18.10.22 20:20 Сейчас в теме
(2)
Никаких included columns нет, В индексе _InfoR18860_MySuperWithoutFld628_ByDims18897_STRRRR поля _Fld628 нет


Тогда не очень понятно, как вообще этот индекс используется, если в нём нет поля по которому фильтрация идёт

(2)
Я в текстовом приложил в конце статьи они хорошо открываются моноширийным шрифтом в 1С . Вам чтото там не хватает ?


Да, не хватает, в xml гораздо больше информации содержится. Да и просто смотреть удобнее
4. 1CUnlimited 323 18.10.22 20:43 Сейчас в теме
Ну в принципе в моей статье есть ответ
В обсуждении на хабре (про Пола Вайта) можете увидеть что это бывает при наличии фрагментации
https://habr.com/ru/post/691326/
XML попробую заново получить как будет время, если обсуждение на хабре не снимет вопросы
5. redfred 19.10.22 06:35 Сейчас в теме
(4)
Ну в принципе в моей статье есть ответ
В обсуждении на хабре (про Пола Вайта) можете увидеть что это бывает при наличии фрагментации


Нет, я про другое немного удивлялся - если в индексе в принципе нет поля _Fld628, то фильтрация по нему в принципе не должна работать в этом индексе, соот. не может получиться такой план, как на скринах вижу. Но с утра, на свежую голову, сообразил, что _Fld628 скорее всего PK (или его часть), а значит всё же там присутствует. Впрочем, на хабре про это уже тоже написали

Вывод: Селективность для оптимизатора MS SQL это уже не основной показатель и как показано ниже для других СУБД тоже. Оптимизатор MS SQL может отказаться использовать селективный индекс даже при сильной фрагментации.


Сама по себе селективность индекса - это сферический конь в вакууме, нужно смотреть на селективность предикатов в запросе, а тут уже на одном и том же индексе селективность может очень сильно гулять. А поскольку оптимизатор в mssql стоимостный, то и планы с разными входными параметрами могут быть сильно разные.
Степень фрагментации,емнип, учитывается достаточно опосредованно - напрямую её оптимизатор не видит, но видит количество страниц в индексе. При прочих равных в сильно фрагментированном их будет больше, стоимость узла увеличится, это тоже может повлиять на итоговый план
6. 1CUnlimited 323 19.10.22 11:55 Сейчас в теме
Не знаю где Вы видите первичный ключ в нетрадиционном индексе,
CREATE NONCLUSTERED INDEX [_InfoR18860_MySuperWithout_Fld628_ByDims18897_STRRRR] ON [dbo].[_InfoRg18860]
(
[_Fld18865] ASC,
[_Period] ASC,
[_Fld18861RRef] ASC,
[_Fld18863RRef] ASC,
[_Fld19363RRef] ASC,
[_Fld18862RRef] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ON LINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [AGRREG]
GO

Стандартный индекс он даже привлекательней поскольку уникальный, но никаких Constraints 1С не создает
/****** Object: Index [_InfoR18860_ByDims18897_STRRRR] Script Date: 10/19/2022 11:26:48 AM ******/
CREATE UNIQUE NONCLUSTERED INDEX [_InfoR18860_ByDims18897_STRRRR] ON [dbo].[_InfoRg18860]
(
[_Fld628] ASC,
[_Fld18865] ASC,
[_Period] ASC,
[_Fld18861RRef] ASC,
[_Fld18863RRef] ASC,
[_Fld19363RRef] ASC,
[_Fld18862RRef] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ON LINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [AGRREG]
GO

Кластерный индекс , в данной ситуации не поможет так как там есть поле [_Fld18861RRef] ASC, -- ИсходнаяСистема , которое в нашем запросе не используется, а оно его разрывает

/****** Object: Index [_InfoR18860_ByPeriod_TRSRRR] Script Date: 10/19/2022 11:28:15 AM ******/
CREATE UNIQUE CLUSTERED INDEX [_InfoR18860_ByPeriod_TRSRRR] ON [dbo].[_InfoRg18860]
(
[_Fld628] ASC,
[_Period] ASC, --Период
[_Fld18861RRef] ASC, -- ИсходнаяСистема
[_Fld18865] ASC, --ИдИсходной системы
[_Fld18863RRef] ASC,
[_Fld19363RRef] ASC,
[_Fld18862RRef] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ON LINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [AGRREG]
GO

Что подразумевается под селективностью предикатов? Rows в фактическом плане? Она тут одинакова, я это показал на картинках со сравнением(+ можно в приложенных файлах посмотреть) разница только в способе выборке и итоговой цене

По стандартному индексу идет |--Index Seek(OBJECT:([MIS_PROD2].[dbo].[_InfoRg18860].[_InfoR18860_ByDims18897_STRRRR] AS [T1]), SEEK:([T1].[_Fld628]=[@P1]), WHERE:([MIS_PROD2].[dbo].[_InfoRg18860].[_Period] as [T1].[_Period]>=[@P2]) ORDERED FORWARD)
По нестандартному индексу идет скан с проверкой всех условий |--Index Scan(OBJECT:([MIS_PROD2].[dbo].[_InfoRg18860].[_InfoR18860_MySuperWithout_Fld628_ByDims18897_STRRRR] AS [T1]), WHERE:([MIS_PROD2].[dbo].[_InfoRg18860].[_Fld628] as [T1].[_Fld628]=[@P1] AND [MIS_PROD2].[dbo].[_InfoRg18860].[_Period] as [T1].[_Period]>=[@P2]) ORDERED FORWARD)

В целом мой посыл таков - раз мы не можем принуждать СУБД хинтами, то единственный вариант повысить эффективность использования индексов это Селективность + Оптимальная структура хранения (в том числе фрагментация), ведь после дефрагментации стандартный селективный индекс опять стал эффективным.

Более интересный вопрос с _Fld628 поле с низкой кардинальностью, стоит ли его ставить всегда в начало индекса как делает 1С? Напр в регистре бухгалтерии присуствует поле Организация, но за счет другой схемы построения стандартных индексов оно не мешает ;)
7. redfred 19.10.22 14:55 Сейчас в теме
(6)
Не знаю где Вы видите первичный ключ в нетрадиционном индексе,


Ок, если уж говорить строго, то не первичный ключ, а ключ кластерного индекса, просто в 99% случаев они совпадают. При создании некластерных индексов он автоматически в них добавляется в качестве указателя на кластерный.


(6)
Что подразумевается под селективностью предикатов? Rows в фактическом плане? Она тут одинакова, я это показал на картинках со сравнением(+ можно в приложенных файлах посмотреть) разница только в способе выборке и итоговой цене


Ну смотрите, например есть у нас колонка _Period, тип datetime, исторических данных много. Селективность более чем хорошая. И в сочетании с условным WHERE _Period = (SELECT GETDATE()), такой и остаётся, поскольку вернёт, скорее всего, от ноля до нескольких строк из нескольких миллионов. Но если в запросе будет WHERE _Period < (SELECT GETDATE()), то селективность данных не будет играть абсолютно никакой роли, потому что с этим предикатом будут выгребаться все строки. Нельзя глядя только на селективность данных сказать, что именно вот такой порядок колонок в индексе предпочтёт оптимизатор. Нужно смотреть на селективность конкретных запросов
8. sulfur17 66 01.06.23 10:10 Сейчас в теме
Буду рад видеть Вас на нашем телеграм-канале

а не подскажете ссылку на ваш ТГ-канал?)
9. 1CUnlimited 323 01.06.23 15:19 Сейчас в теме
(8) t.me/Chat1CUnlimited и в профиле тоже есть
Оставьте свое сообщение