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

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.

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

Thinkinger
источник
Можете ли вы опубликовать фактический план выполнения 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). У вас нет выбора здесь.

USR
источник
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