Saco de areia
Enquanto trabalhava no Top Quality Blog Posts® , me deparei com alguns comportamentos do otimizador que achei muito interessantes. Eu não tenho uma explicação imediatamente, pelo menos não uma com a qual eu esteja 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 eu consulto a tabela assim, recebo um plano de consulta estranho .
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 em Score não é inserido no CTE. Está em um operador de filtro muito mais tarde no plano.
O que eu acho estranho, já que o ORDER BY
está na mesma coluna que o filtro.
Consulta Dois
Se eu alterar a consulta, ela é 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 clusterizado.
Consulta três
Isso é o equivalente a escrever a consulta assim:
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 "ruim" que a 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...
Eu altero a consulta para ordenar os dados em ordem crescente e o filtro para <=
.
Para evitar fazer essa pergunta muito longa, vou colocar tudo junto.
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.
Aparece uma pergunta!
Existe uma razão pela qual um predicado SARGable pode ser empurrado em alguns cenários e não em outros? As diferenças dentro das consultas classificadas em ordem decrescente são interessantes, mas as diferenças entre aquelas e as que são ascendentes são bizarras.
Para quem estiver interessado, aqui estão os planos com apenas um índice em Score
:
Há alguns problemas em jogo aqui.
Empurrando predicados passados
TOP
Atualmente, o otimizador não pode enviar um predicado além de um
TOP
, mesmo nos casos limitados em que seria seguro fazê-lo*. Essa limitação explica o comportamento de todas as consultas na pergunta em que o predicado está em um escopo mais alto que oTOP
.A solução alternativa é executar a reescrita manualmente. A questão fundamental é semelhante ao caso de empurrar predicados além de uma função de janela , exceto que não há uma regra especializada correspondente como
SelOnSeqPrj
.Minha opinião pessoal é que uma regra de exploração como
SelOnTop
permanece não implementada porque as pessoas escreveram consultas deliberadamenteTOP
em um esforço para fornecer uma espécie de 'cerca de otimização'.* Geralmente, isso significa que o predicado deve aparecer na
ORDER BY
cláusula associada aoTOP
, e a direção de qualquer desigualdade deve concordar com a direção da classificação. A transformação também precisaria levar em conta o comportamento de classificação de NULLs no SQL Server. No geral, as limitações provavelmente significam que essa transformação geralmente não seria útil o suficiente na prática para justificar os esforços adicionais de exploração.Problemas de custo
Os planos de execução restantes na pergunta podem ser explicados como escolhas baseadas em custo devido à distribuição de valores na
Score
coluna (muito mais linhas <= 500 do que >= 500) e o efeito da meta de linha introduzida peloTOP
.Por exemplo, a consulta:
... produz um plano com um predicado aparentemente não enviado em um Filtro:
Observe que o Sort é estimado para produzir 101 linhas. Este é o efeito do objetivo de linha adicionado pelo Top. Isso afeta o custo estimado do Sort e do Filtro o suficiente para fazer parecer que esta é a opção mais barata. O custo estimado deste plano é de 2.401,39 unidades.
Se desabilitarmos as metas de linha com uma dica de consulta:
...o plano de execução produzido é:
O predicado foi inserido na varredura como um predicado residual não sargável e o custo de todo o plano é de 2.402,32 unidades.
Observe que
<= 500
não se espera que o predicado filtre nenhuma linha. Se você tivesse escolhido um número menor, como<= 50
, o otimizador teria preferido o plano de predicado enviado, independentemente do efeito da meta de linha.Para a consulta com
Score DESC
e umScore >= 500
predicado:Agora, espera-se que o predicado seja muito seletivo, portanto, o otimizador opta por enviar o predicado e usar o índice não clusterizado com pesquisas:
Novamente, o otimizador considerou várias alternativas e escolheu esta como a opção aparentemente mais barata, como de costume.