Порядок полей в порядке составного индекса с полями высокой селективности и низкой селективности


11

У меня есть таблица SQL Server с более чем 3 миллиардов строк. Один из моих запросов занимает очень много времени, поэтому я рассматриваю возможность его оптимизации. Запрос выглядит так:

SELECT [Enroll_Date]
      ,Count(*) AS [Record #]
      ,Count(Distinct UserID) AS [User #]
  FROM UserTable
  GROUP BY [Enroll_Date]

[Enroll_Date] - это столбец с низкой селективностью, содержащий менее 50 возможных значений, а столбец UserID - это столбец с высокой селективностью, содержащий более 200 миллионов различных значений. Основываясь на моих исследованиях, я считаю, что я должен создать некластеризованный составной индекс для этих двух столбцов, и теоретически столбец высокой селективности должен быть первым столбцом. Но я не уверен, что в моем случае это сработает, потому что я использую столбец низкой селективности в предложении group by.

Эта таблица не имеет кластеризованного индекса.


Можете ли вы опубликовать фактический план выполнения XML (использовать pastebin и связать его здесь)? Какую версию сервера SQL вы используете?
Кин Шах

3
Индекс с высокоселективным столбцом первым будет бесполезен для конкретного запроса.
ypercubeᵀᴹ

Рекомендуется использовать столбец с более высокой избирательностью в качестве первого ключевого столбца в индексе (обычно). В этом сценарии, как вы уже догадались, он вам совсем не поможет. Вам могут понадобиться два индекса! Что происходит, когда вы используете enroll_date первым и user_id вторым?
Паульбарбин

Ответы:


12

В качестве альтернативы решению @ AaronBertrand (если вы не можете или не хотите создавать индексированное представление), я бы порекомендовал вам создать индекс (Enroll_Date, UserID). Если этот тип вопросов очень распространен в вашей таблице, это, вероятно, даже должен быть ваш кластерный индекс.

Я бы не стал рекомендовать индексы высокой селективности в качестве общей «наилучшей практики», а скорее посмотрю, какой индекс даст вашему запросу наилучшую производительность.

Индекс на (Enroll_Date, UserID)даст вашему запросу высоко оптимизированный, неблокирующий план запроса с агрегатами потоков.

План потоковых агрегатных запросов

«Неблокирующая» в этом контексте означает, что запросу не нужно буферизовать какие-либо значительные объемы данных (как, например, сортировка или агрегат хэшей), что означает, что он (а) немедленно начинает возвращать строки, и ( б) практически не потребляет рабочую память.


Забавно, с интервалом 4 секунды и тот же ответ.
USR

11

Ответ Аарона - отличное решение. Я отвечу на вопрос, если вы не хотите использовать этот подход.

Запрос, который вы разместили, будет обычно выполняться сначала в группе (Enroll_Date, UserID), а затем снова (Enroll_Date). Эта оптимизация является новой для SQL Server 2012. Она вступает в силу в случае одного COUNT DISTINCT.

Индекса для этих двух столбцов в определенном порядке (Enroll_Date, UserID)будет достаточно для получения эффективного плана, который объединяет сканирование индекса в два последовательных потоковых агрегата. Противоположный порядок не позволил бы этот план.

Поэтому используйте порядок (Enroll_Date, UserID). У вас нет выбора здесь.


5 секунд и то же решение. Хорошо сыграно, сэр. :)
Даниэль Хутмахер

@DanielHutmacher OMG, мы сможем почти соответствовать нашим постам в третий раз ?! +1 тебе! Как я могу не дать одинаковый ответ?
USR

Глюк в матрице. :)
Даниэль Хутмахер

Большое спасибо. Я создаю индекс и опубликую улучшение после его завершения. Версия сервера - Microsoft SQL Server 2008 R2 на AWS, но я думаю, что это все еще единственный выбор, несмотря на это.
Thinkinger

@Thinkinger в случае , если вы не принимаете Ааронс подходить у вас есть жесткий выбор :)
ЕГР

11

Походит на идеальный сценарий для индексированного представления, которое позволяет вам платить за вычисления и агрегаты во время записи вместо времени запроса.

CREATE VIEW dbo.MyIndexedView
WITH SCHEMABINDING
AS 
  SELECT Enroll_Date, UserID, RawCount = COUNT_BIG(*)
  FROM dbo.UserTable
  GROUP BY Enroll_Date, UserID;
GO

CREATE UNIQUE CLUSTERED INDEX CIX_miv ON dbo.MyIndexedView(Enroll_Date, UserID);

Это займет некоторое время для создания и, конечно, потребует сопровождения во всех операциях DML, точно так же, как индекс в базовой таблице.

Теперь запрос к этому представлению будет очень похожим - каждая строка в представлении теперь представляет отдельную комбинацию пользователя / даты, так что цифра может быть вычислена по одному COUNT (*), тогда как общее количество строк в базовой таблице равно уже частично агрегированы для вас, теперь вам просто нужно добавить их, используя SUM на дату:

SELECT Enroll_Date, 
  [Record #] = SUM(RawCount),
  [User #] = COUNT(*)
FROM dbo.MyIndexedView WITH (NOEXPAND)
GROUP BY Enroll_Date; 

Добавлена ​​подсказка NOEXPAND, после запоминания этого и этого .

Я могу безоговорочно сказать вам, что этот запрос будет быстрее, чем ваш текущий запрос (но не на сколько), за исключением редкого случая, когда у вас есть ровно один пользователь на каждую дату (в этом случае тот же объем данных будет иметь для чтения), и столбцы, о которых мы знаем, являются единственными столбцами в индексе базовой таблицы. О том, стоит ли повышение производительности во время чтения дополнительной работы, которая повлияет на часть записи вашей рабочей нагрузки, мы не можем вам сказать - вам придется протестировать ее, чтобы измерить компромисс (никакой индекс не является бесплатным).

И если вы часто используете одни и те же общие предложения WHERE для Enroll_Date для конкретных, четко определенных диапазонов (скажем, текущего квартала или года до даты), вы можете добавить соответствующие отфильтрованные индексы, которые еще больше уменьшат этот ввод / вывод (но всегда есть компромисс).

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


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

1
Считает ли ваша ИТ существенная разница между индексированным представлением и дополнительными или разными индексами в базовой таблице? Не быть боевым, просто любопытным, потому что многие люди имеют неправильные представления об индексированных представлениях. Мне нравится думать о них как о дополнительном, более узком кластерном индексе в таблице, но с меньшим количеством строк.
Аарон Бертран

@ Thinkinger также, индексированные представления не только для EE. Соответствие индексированного представления только для EE. Вы можете напрямую нацелить их, используя NOEXPAND.
USR
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.