Согласно этому ответу , если индекс не построен по столбцам, которые используются для ограничения, запрос не получит выгоды от индекса.
У меня есть это определение:
CREATE TABLE [dbo].[JobItems] (
[ItemId] UNIQUEIDENTIFIER NOT NULL,
[ItemState] INT NOT NULL,
[ItemPriority] INT NOT NULL,
[CreationTime] DATETIME NULL DEFAULT GETUTCDATE(),
[LastAccessTime] DATETIME NULL DEFAULT GETUTCDATE(),
-- other columns
);
CREATE UNIQUE CLUSTERED INDEX [JobItemsIndex]
ON [dbo].[JobItems]([ItemId] ASC);
GO
CREATE INDEX [GetItemToProcessIndex]
ON [dbo].[JobItems]([ItemState], [ItemPriority], [CreationTime])
INCLUDE (LastAccessTime);
GO
и этот запрос:
UPDATE TOP (150) JobItems
SET ItemState = 17
WHERE
ItemState IN (3, 9, 10)
AND LastAccessTime < DATEADD (day, -2, GETUTCDATE())
AND CreationTime < DATEADD (day, -2, GETUTCDATE());
Я пересмотрел фактический план, и есть только один индексный поиск с предикатом, точно такой же, как в WHERE
- никаких дополнительных «просмотров закладок» для извлечения, LastAccessTime
даже если последний только «включен» в индекс, а не в его часть.
Мне кажется, что это поведение противоречит правилу, согласно которому столбец должен быть частью индекса, а не просто «включен».
Правильное ли поведение я наблюдаю? Как я могу знать заранее, если мои WHERE
выгоды от включенного столбца или нужно, чтобы столбец был частью индекса?
sql-server
index
execution-plan
azure-sql-database
Sharptooth
источник
источник
ItemState
значении, однако поиск не будет таким эффективным, как если бы ваш индекс был структурирован следующим образом(ItemState, CreationTime, LastAccessTime)
(ItemState, CreationTime) INCLUDE (LastAccessTime)
(a,b)
не является лучшим для запроса с,SELECT a FROM t WHERE b=5;
и что индекс на(b) INCLUDE (a)
гораздо лучше.Ответы:
Ваш предикат отличается от вашего предиката поиска.
Предикат поиска используется для поиска упорядоченных данных в индексе. В этом случае он будет выполнять три поиска, по одному для каждого ItemState, в котором вы заинтересованы. Кроме того, данные располагаются в порядке ItemPriority, поэтому дальнейшая операция «Поиск» невозможна.
Но перед возвратом данных он проверяет каждую строку, используя Предикат, который я называю Остаточным Предикатом. Это сделано по результатам поиска предиката.
Любой включенный столбец не является частью упорядоченных данных, но может использоваться для удовлетворения остаточного предиката без необходимости дополнительного поиска.
Вы можете увидеть материал, который я написал по этому поводу вокруг Sargability. В частности, проверьте сеанс в SQLBits, по адресу http://bit.ly/Sargability
Изменить: чтобы лучше показать влияние остатков, запустите запрос, используя недокументированный документ
OPTION (QUERYTRACEON 9130)
, который разделит остаток на отдельный оператор фильтра (который на самом деле является более ранней версией плана до того, как остаток будет перемещен в оператор поиска). Он ясно показывает влияние неэффективного поиска по количеству строк, переданных в фильтр.Также стоит отметить, что из-за предложения IN в ItemState передаваемые данные в действительности находятся в порядке ItemState, а не в ItemPriority. Составной индекс ItemState, за которым следует одна из дат (например, (ItemState, LastAccessTime)), можно использовать для трех поисков (обратите внимание, что предикат поиска показывает три поиска в одном операторе поиска), каждый для двух уровней, что приводит к получению данных, которые по-прежнему в порядке ItemState (например, ItemState = 3 и LastAccessTime меньше чем-то, затем ItemState = 9 и LastAccessTime меньше чем-то, а затем ItemState = 10 и LastAccessTime меньше чем-то).
Индекс на (ItemState, LastAccesTime, CreationTime) будет не более полезным, чем индекс на (ItemState, LastAccessTime), поскольку уровень CreationTime полезен только в том случае, если ваш Seek предназначен для определенной комбинации ItemState и LastAccessTime, а не для диапазона. Например, телефонная книга не в порядке FirstName, если вас интересуют фамилии, начинающиеся с F.
Если вам нужен составной индекс, но вы никогда не сможете использовать более поздние столбцы в поиске предикатов из-за того, как вы используете более ранние столбцы, то вы можете также использовать их как включенные столбцы, где они занимают меньше места в index (поскольку они хранятся только на уровне листа индекса, а не на более высоких уровнях), но все же могут избегать поиска и использоваться в остаточных предикатах.
Согласно термину Остаточный предикат - это мой собственный термин для этого свойства поиска. Соединение слиянием в явном виде называет его эквивалентом остаточный предикат, а хэш-совпадение называет его остаточным предикатом (который вы можете получить от TSA, если сопоставите хеш). Но в Поисках они просто называют это Предикатом, что делает его менее плохим, чем есть.
источник
GetItemToProcessIndex не полностью доступен для поиска, потому что включено условие where
ItemState + LastAccessTime + CreationTime
. Индексированные столбцы и предложение where не идеально совпадают.Если вы создаете индекс покрытия
ItemState + LastAccessTime + CreationTime
для каждого совпадения, получаемого из GetItemToProcessIndex, вы также получаете значение вашего первичного ключа (ItemId). Нужно только убедиться, что вторая дата совпадает.Это все, что вам нужно, чтобы затем перейти к расположению строки на ее странице и обновить ее.
С вашим текущим индексом он может помочь серверу найти строки с нужным вам ItemState, но ему все равно придется прочитать все из индекса, чтобы найти правильные совпадения в LastAccessTime + CreationTime. В зависимости от предикатов даты и размера соответствующего набора и того, что должно быть исключено, это может привести к намного большему количеству операций ввода-вывода, чем идеально охватывающий индекс только для 3 столбцов, который будет искать ItemState и второй столбец (1-я индексированная дата) , Вторая дата в индексированном может быть включена, хотя. Дополнительные столбцы не должны индексироваться между этими 3, хотя это может быть хорошо, как 4-й столбец (см. Ответ Роба о дополнительных столбцах).
источник