Dado o seguinte exemplo:
IF OBJECT_ID('dbo.my_table') IS NOT NULL
DROP TABLE [dbo].[my_table];
GO
CREATE TABLE [dbo].[my_table]
(
[id] int IDENTITY (1,1) NOT NULL PRIMARY KEY,
[foo] int NULL,
[bar] int NULL,
[nki] int NOT NULL
);
GO
/* Insert some random data */
INSERT INTO [dbo].[my_table] (foo, bar, nki)
SELECT TOP (100000)
ABS(CHECKSUM(NewId())) % 14,
ABS(CHECKSUM(NewId())) % 20,
n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
FROM
sys.all_objects AS s1
CROSS JOIN
sys.all_objects AS s2
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_my_table]
ON [dbo].[my_table] ([nki] ASC);
GO
Se eu buscar todos os registros ordenados por [nki]
(índice não clusterizado):
SET STATISTICS TIME ON;
SELECT id, foo, bar, nki FROM my_table ORDER BY nki;
SET STATISTICS TIME OFF;
SQL Server Execution Times: CPU time = 266 ms, elapsed time = 493 ms
O otimizador escolhe o índice clusterizado e, em seguida, aplica um algoritmo de classificação.
Mas se eu forçá-lo a usar o índice não clusterizado:
SET STATISTICS TIME ON;
SELECT id, foo, bar, nki FROM my_table WITH(INDEX(IX_my_TABLE));
SET STATISTICS TIME OFF;
SQL Server Execution Times: CPU time = 311 ms, elapsed time = 188 ms
Em seguida, ele usa o índice não clusterizado com uma pesquisa de chave:
Obviamente, se o índice não clusterizado for transformado em um índice de cobertura:
CREATE UNIQUE NONCLUSTERED INDEX [IX_my_table]
ON [dbo].[my_table] ([nki] ASC)
INCLUDE (id, foo, bar);
GO
Em seguida, ele usa apenas este índice:
SET STATISTICS TIME ON;
SELECT id, foo, bar, nki FROM my_table ORDER BY nki;
SET STATISTICS TIME OFF;
SQL Server Execution Times: CPU time = 32 ms, elapsed time = 106 ms
Pergunta
- Por que o SQL Server usa o índice clusterizado mais um algoritmo de classificação em vez de usar um índice não clusterizado, mesmo que o tempo de execução seja 38% mais rápido no último caso?
Se você comparar o número de leituras necessárias em 100.000 pesquisas com o que está envolvido em uma classificação, poderá obter rapidamente uma ideia sobre por que o Otimizador de consulta calcula que o CIX+Sort seria a melhor escolha.
A execução do Lookup acaba sendo mais rápida porque as páginas que estão sendo lidas estão na memória (mesmo se você limpar o cache, você tem muitas linhas por página, então você está lendo as mesmas páginas repetidamente, mas com diferentes quantidades de fragmentação ou pressão de memória diferente de outra atividade, isso pode não ser o caso). Realmente não precisaria muito para que o CIX+Sort fosse mais rápido, mas o que você está vendo é porque o custo de uma leitura não leva em consideração o relativo baixo custo de acessar as mesmas páginas repetidamente.
Como o SQL Server usa um otimizador baseado em custo com base em estatísticas, não em informações de tempo de execução.
Durante o processo de estimativa de custo para essa consulta, ele realmente avalia o plano de pesquisa, mas estima que exigirá mais esforço. (Observe o "Custo estimado da subárvore" ao passar o mouse sobre SELECT no plano de execução). Isso também não é necessariamente uma suposição ruim - na minha máquina de teste, o plano de pesquisa leva 6X a CPU da classificação/varredura.
Veja a resposta de Rob Farley sobre por que o SQL Server pode custar mais caro ao plano de pesquisa.
Resolvi me aprofundar um pouco nessa questão e descobri alguns documentos interessantes falando sobre como e quando usar ou talvez melhor, não (forçar o) uso de um índice não clusterizado.
Como sugerido por comentários de John Eisbrener , um dos mais referenciados, inclusive em outros blogs, é este interessante artigo de Kimberly L. Tripp:
mas não é o único, se você estiver interessado pode dar uma olhada nestas páginas:
Como você pode ver, todos eles se movem em torno do conceito de Ponto de Virada .
Citado no artigo de KL Tripp
Quando o SQL Server usa um índice não clusterizado em um heap, basicamente ele obtém uma lista de ponteiros para as páginas da tabela base. Em seguida, ele usa esses ponteiros para recuperar as linhas com uma série de operações chamadas Row ID Lookups (RID). Isso significa que, pelo menos, ele usará tantas leituras de página quanto o número de linhas retornadas e talvez mais. O processo é um pouco semelhante com um índice clusterizado como a tabela base, com o mesmo resultado: mais leituras.
Mas, quando esse ponto de inflexão ocorre?
Claro que como a maioria das coisas nesta vida, depende...
Não sério, isso ocorre entre 25% e 33% do número de páginas na tabela, dependendo de quantas linhas por página. Mas há mais fatores que você deve considerar:
Citado no artigo do ITPRoToday
Agora, se eu executar minhas consultas novamente usando estatísticas IO:
A segunda consulta precisa de mais leituras lógicas do que a primeira.
Devo evitar o índice não clusterizado?
Não, um índice clusterizado pode ser útil, mas vale a pena dedicar algum tempo e fazer um esforço extra analisando o que você está tentando alcançar com ele.
Citado no artigo de KL Tripp