Saco de areia
Enquanto trabalhava no Top Quality Blog Posts®, me deparei com um comportamento otimizador que achei realmente irritante interessante. Eu não tenho uma explicação imediata, pelo menos não uma com a qual estou feliz, então estou colocando aqui caso alguém inteligente apareça.
Se você quiser acompanhar, você pode pegar a versão 2013 do despejo de dados do Stack Overflow aqui . Estou usando a tabela Comentários, com um índice adicional.
CREATE INDEX [ix_ennui] ON [dbo].[Comments] ( [UserId], [Score] DESC );
Consulta Um
Quando consulta a tabela dessa maneira, recebo um plano de consulta ímpar .
WITH x
AS
(
SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
ORDER BY c.Score DESC
)
SELECT *
FROM x
WHERE x.Score >= 500;
O predicado SARGable no Score não é enviado para dentro do CTE. Está em um operador de filtro muito mais tarde no plano.
O que acho estranho, já que o ORDER BY
está na mesma coluna do filtro.
Consulta Dois
Se eu alterar a consulta, ela será enviada.
WITH x
AS
(
SELECT c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
)
SELECT TOP 101 *
FROM x
WHERE x.Score >= 500
ORDER BY x.Score DESC;
O plano de consulta também muda e é executado muito mais rápido, sem derramamento no disco. Ambos produzem os mesmos resultados, com o predicado na varredura de índice não clusterizada.
Consulta Três
Isso é o equivalente a escrever a consulta da seguinte maneira:
SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
WHERE c.Score >= 500
ORDER BY c.Score DESC;
Consulta Quatro
O uso de uma tabela derivada obtém o mesmo plano de consulta "inválido" da consulta CTE inicial
SELECT *
FROM ( SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
ORDER BY c.Score DESC ) AS x
WHERE x.Score >= 500;
As coisas ficam ainda mais estranhas quando ...
Altero a consulta para ordenar os dados ascendentes e o filtro para <=
.
Para não exagerar nessa pergunta, vou juntar tudo.
Consultas
--Derived table
SELECT *
FROM ( SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
ORDER BY c.Score ASC ) AS x
WHERE x.Score <= 500;
--TOP inside CTE
WITH x
AS
(
SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
ORDER BY c.Score ASC
)
SELECT *
FROM x
WHERE x.Score <= 500;
--Written normally
SELECT TOP 101
c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
WHERE c.Score <= 500
ORDER BY c.Score ASC;
--TOP outside CTE
WITH x
AS
(
SELECT c.UserId, c.Text, c.Score
FROM dbo.Comments AS c
)
SELECT TOP 101 *
FROM x
WHERE x.Score <= 500
ORDER BY x.Score ASC;
Planos
Observe que nenhuma dessas consultas tira proveito do índice não clusterizado - a única coisa que muda aqui é a posição do operador de filtro. Em nenhum caso o predicado é enviado para o acesso ao índice.
Uma pergunta aparece!
Existe uma razão para que um predicado SARGable possa ser enviado em alguns cenários e não em outros? As diferenças nas consultas classificadas em ordem decrescente são interessantes, mas as diferenças entre essas e as que estão subindo são bizarras.
Para quem estiver interessado, aqui estão os planos com apenas um índice Score
: