Порядок индекса SQL Server (поле даты и времени) - PullRequest
7 голосов
/ 09 июля 2009

У меня вопрос по индексам SQL Server. Я не администратор базы данных и предполагаю, что ответ ясен для тех из вас, кто есть. Я использую SQL Server 2008.

У меня есть таблица, которая похожа на следующую (но имеет больше столбцов):

CREATE TABLE [dbo].[Results](
    [ResultID] [int] IDENTITY(1,1) NOT NULL,
    [TypeID] [int] NOT NULL,
    [ItemID] [int] NOT NULL,
    [QueryTime] [datetime] NOT NULL,
    [ResultTypeID] [int] NOT NULL,
    [QueryDay]  AS (datepart(day,[querytime])) PERSISTED,
    [QueryMonth]  AS (datepart(month,[querytime])) PERSISTED,
    [QueryYear]  AS (datepart(year,[querytime])) PERSISTED,
 CONSTRAINT [PK_Results] PRIMARY KEY CLUSTERED 
(
    [ResultID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]

Важные поля, на которые следует обратить внимание: ResultID, первичный ключ, и QueryTime - дата и время, когда был получен результат.

У меня также есть следующий индекс (среди прочих):

CREATE NONCLUSTERED INDEX [IDX_ResultDate] ON [dbo].[Results] 
(
    [QueryTime] ASC
)
INCLUDE ( [ResultID],
[ItemID],
[TypeID]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 90) ON [PRIMARY]

В базе данных, где у меня около миллиона строк в таблице, индекс используется при выполнении запроса, такого как:

select top 1 * from results where querytime>'2009-05-01' order by ResultID asc

В другом экземпляре той же базы данных с 50 миллионами строк SQL Server решает не использовать индекс, а скорее выполняет сканирование кластеризованных индексов, которое оказывается ужасно медленным. (и скорость зависит от даты). Даже если я использую подсказки запроса, чтобы заставить его использовать IDX_ResultDate, он все еще немного медленный и тратит 94% своего времени на сортировку по ResultID. Я подумал, что, создав индекс с ResultID и QueryTime в качестве отсортированных столбцов в индексе, я смог ускорить свой запрос.

Поэтому я создал следующее:

CREATE NONCLUSTERED INDEX [IDX_ResultDate2] ON [dbo].[Results] 
(
[QueryTime] ASC,    
[ResultID] ASC
)
INCLUDE ( [ItemID],
[TypeID]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 90) ON [PRIMARY]
GO

Я предполагал, что сначала будет использоваться сортировка по QueryTime, чтобы найти совпадающие результаты, которые уже будут отсортированы по ResultID. Однако это не так, так как этот индекс не меняет производительности по сравнению с существующим.

Затем я попробовал следующий индекс:

CREATE NONCLUSTERED INDEX [IDX_ResultDate3] ON [dbo].[Results] 
(
    [ResultID] ASC,
    [QueryTime] ASC
)
INCLUDE ( [ItemID],
[TypeID]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 90) ON [PRIMARY]
GO

Этот дает ожидаемый результат. Похоже, чтобы вернуться в постоянное время (доли секунды).

Однако я озадачен, почему IDX_ResultDate3 работает хорошо, тогда как IDX_ResultDate2 - нет.

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

Дополнительный вопрос: должен ли я создать постоянный столбец с частью даты QueryTime и индексом по ней (у меня уже есть три постоянных столбца, как вы можете видеть выше)?

Ответы [ 5 ]

12 голосов
/ 09 июля 2009

Я бы предположил, что бинарный поиск в как отсортированный список QueryTime следуют посмотрев на первый результат в это дочерний список ResultIDs является самым быстрым способ получения результата. (Отсюда мой начальный порядок сортировки).

Это действительно было бы быстро, но ваш запрос выражает другой запрос: вы запрашиваете Result с минимальным ResultId из всех запросов, которые произошли после '2009-05-01' . Чтобы удовлетворить запрос, который он должен искать в начале диапазона ('2009-05-01'), запустите сканирование с этой позиции, чтобы извлечь все ResultId, отсортируйте их, а затем верните верхний 1 (минимальный ResultId). Второй индекс, который вы добавили [idx_ResultDate2], тоже мало чем поможет. Запрос должен выполнять почти одинаковые операции поиска и сканирования: ResultIds отсортированы с датой результата , поэтому для определения верхнего ResultId из всех результатов, полученных после '2009 г. -05-01 'запрос все еще должен сканировать индекс до конца.

В вашем последнем индексе, [IDX_ResultDate3], запрос обманывает. Что он делает, он запускает сканирование Inde и просматривает значение QueryTime, зная, что в этом индексе сканируется результат first , в котором QueryTime находится в требуемом диапазоне (> '2009-05-01' ) тот, который вы хотите (потому что ResultId гарантированно будет первым). Вы получаете результат в доли секунды из чистой удачи: у вас есть соответствующий результат в начале индекса. Запрос вполне может сканировать весь индекс и соответствовать самому последнему результату. Вы можете вставить новый Result с QueryTime, например «2010-01-01», а затем искать его, и вы увидите, что производительность снижается, так как запрос должен сканировать весь индекс до конца (все же быстрее, чем сканирование таблицы, потому что более узкого размера индекса).

Мой вопрос: вы абсолютно уверены, что ваш запрос должен вернуть ТОП 1 в ORDER BY ResultID? Или вы просто выбрали заказ произвольно? Если вы можете изменить запрос ORDER BY, скажем, на QueryTime, то любой из индексов ( обновленный : с QueryTime в качестве крайнего левого столбца) вернет простой поиск и выборку, без сканирования и без сортировки.

4 голосов
/ 09 июля 2009

У вас есть условие фильтрации в диапазоне для одного поля вместе с ORDER BY для другого поля.

Индекс, даже составной индекс, не может использоваться для выполнения обоих условий в этом случае.

При создании индекса на (queryTime, resultId) индекс используется для фильтрации. Двигатель все еще нуждается в заказе результирующего набора.

Когда вы создаете индекс для (resultId, queryTime), этот индекс используется для упорядочения.

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

Если ваше условие фильтрации будет селективным (т. Е. Оно будет возвращать несколько строк), и первый нужный вам результат окажется в конце индекса, первый подход будет лучше. 1019 *

См. Эту статью в моем блоге для получения дополнительных объяснений и подсказок, какой индекс создать в каких условиях:

2 голосов
/ 09 июля 2009

Вы можете изменить кластеризованный индекс на ([QueryTime], [ResultID]) или изменить свой запрос с

select top 1 * from results where querytime>'2009-05-01' order by ResultID asc

до

select top 1 <only the columns you actually need> from results where querytime>'2009-05-01' order by ResultID asc

и включите все эти столбцы в [IDX_ResultDate2]

0 голосов
/ 10 июля 2009

Я не уверен, что могу ответить на этот вопрос, но хотел бы указать, что ключ кластеризованного индекса уже включен как часть любого другого индекса, поэтому его избыточно включать ResultID в качестве части любого из других предлагаемых вами индексов.

0 голосов
/ 09 июля 2009

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

Поскольку у вас есть два разных плана выполнения с разными наборами данных, кажется, что SQL Server делает позорный «суждение» при выборе одного плана выполнения над другим.

Я согласен с объяснением Ремуса о том, почему вы получаете «волшебные» результаты с вашим последним индексом.

Его предложение также хорошо - вы действительно хотите заказать по resultID? Или, если вы можете упорядочить по queryTime, то у вас будет НАМНОГО лучшая производительность, потому что план выполнения сможет использовать порядок индексов в качестве порядка набора результатов (И он будет искать по индексу вместо сканирования).

...