De acordo com esta resposta , a menos que um índice seja criado sobre as colunas usadas para restringir, a consulta não será beneficiada por um índice.
Eu tenho esta definição:
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
e esta consulta:
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());
Revisei o plano real e há apenas uma pesquisa de índice com o predicado exatamente como em WHERE
- nenhuma "pesquisa de favoritos" adicional a ser recuperada, LastAccessTime
mesmo que a última seja apenas "incluída" no índice, não parte do índice.
Parece-me que esse comportamento contradiz a regra de que a coluna deve fazer parte do índice, e não apenas "incluída".
O comportamento que observo é o correto? Como posso saber com antecedência se meus WHERE
benefícios de uma coluna incluída ou precisam que a coluna faça parte do índice?
sql-server
index
execution-plan
azure-sql-database
dente afiado
fonte
fonte
ItemState
valor, no entanto, a busca não será tão eficiente como se o seu Índice foi estruturado da seguinte forma(ItemState, CreationTime, LastAccessTime)
(ItemState, CreationTime) INCLUDE (LastAccessTime)
(a,b)
não é o melhor para uma consultaSELECT a FROM t WHERE b=5;
e que um índice ativado(b) INCLUDE (a)
é muito melhor.Respostas:
Seu Predicado é diferente do seu Predicado de Procura.
Um Predicado de busca é usado para pesquisar os dados ordenados no índice. Nesse caso, serão realizadas três buscas, uma para cada ItemState em que você está interessado. Além disso, os dados estão na ordem de ItemPriority, para que nenhuma operação "Busca" seja possível.
Porém, antes que os dados sejam retornados, ele verifica todas as linhas usando o Predicado, ao qual me refiro como Predicado Residual. É feito com os resultados do Predicado de busca.
Qualquer coluna incluída não faz parte dos dados solicitados, mas pode ser usada para satisfazer o Predicado Residual, sem ter que fazer a Pesquisa extra.
Você pode ver o material que escrevi sobre Sargability. Verifique se há uma sessão no SQLBits em particular, em http://bit.ly/Sargability
Editar: para mostrar melhor o impacto dos resíduos, execute a consulta usando o não documentado
OPTION (QUERYTRACEON 9130)
, que separará o resíduo em um operador de filtro separado (que na verdade é uma versão anterior do plano antes que o resíduo seja movido para o operador de busca). Ele mostra claramente o impacto de uma busca ineficaz pelo número de linhas sendo deixadas para o filtro.Também vale a pena notar que, devido à cláusula IN no ItemState, os dados que estão sendo deixados na verdade estão na ordem ItemState, não na ordem ItemPriority. Um índice composto no ItemState seguido por uma das datas (por exemplo (ItemState, LastAccessTime)) pode ser usado para ter três buscas (observe que o Predicado de busca mostra três buscas no operador de busca), cada uma contra dois níveis, produzindo dados que são ainda na ordem ItemState (por exemplo, ItemState = 3 e LastAccessTime menor que algo, então ItemState = 9 e LastAccessTime menor que algo e, em seguida, ItemState = 10 e LastAccessTime menor que algo).
Um índice em (ItemState, LastAccesTime, CreationTime) não seria mais útil do que um em (ItemState, LastAccessTime) porque o nível CreationTime só será útil se sua Busca for para uma combinação específica ItemState e LastAccessTime, e não um intervalo. Como a lista telefônica não está na ordem do nome, se você estiver interessado em sobrenomes que começam em F.
Se você deseja um índice composto, mas nunca poderá usar as colunas posteriores nos Seek Predicates, devido à maneira como usa as colunas anteriores, também poderá tê-las como colunas incluídas, onde elas ocupam menos espaço no index (porque eles são armazenados apenas no nível da folha do índice, não nos níveis mais altos), mas ainda podem evitar pesquisas e se acostumar nos predicados residuais.
De acordo com o termo Predicado Residual - esse é o meu próprio termo para esta propriedade de uma Busca. Um Merge Join o chama explicitamente de Predicado Residual equivalente, e o Hash Match o chama de Probe Residual (que você pode obter da TSA se corresponder a hash). Mas, em uma Busca, eles chamam de Predicado, o que faz com que pareça menos ruim do que é.
fonte
GetItemToProcessIndex não é totalmente procurável porque sua cláusula where está ativada
ItemState + LastAccessTime + CreationTime
. As colunas indexadas e a cláusula where não são uma combinação perfeita.Se você criar um índice de cobertura
ItemState + LastAccessTime + CreationTime
, para cada correspondência obtida em GetItemToProcessIndex, também obtém o valor da sua Chave Primária (ItemId). Ele só precisa garantir que a segunda data seja uma partida.É tudo o que você precisa para ir para o local da linha em sua página e atualizá-lo.
Com o índice atual, pode ajudar o servidor a encontrar linhas com o ItemState desejado, mas ainda assim precisa ler todas elas no índice para encontrar correspondências corretas no LastAccessTime + CreationTime. Dependendo dos predicados de data e do tamanho do conjunto de correspondências e do que deve ser excluído, pode resultar em muito mais IO do que um índice de cobertura perfeita nas 3 colunas apenas que buscariam o ItemState e a segunda coluna (1ª data indexada) . A segunda data no indexado pode ser incluída. Colunas extras não devem ser indexadas entre essas 3, embora possa estar ok como uma quarta coluna (consulte a resposta de Rob sobre colunas extras).
fonte