Por que meu ORDER BY classifica duas tabelas antes de EXCEPT (lento) e não depois (rápido)?

12

Puzle Otimizador de consultas do SQL Server 2008 R2

Temos duas tabelas, ambas contendo 9 milhões de linhas. 70.000 linhas são diferentes, as outras são iguais.

Isso é rápido, 13 segundos,

select * from bigtable1
except select * from similar_bigtable2

Isso classifica a saída e também é rápido, 13 segundos também,

select * into #q from bigtable1
except select * from similar_bigtable2
select * from #q order by sort_column

Embora isso seja muito lento:

;with q as (
    select * from bigtable1
    except select * from similar_bigtable2
)
select * from q order by sort_column

E mesmo um "truque" que às vezes uso para sugerir ao SQL Server que ele precisa pré-calcular uma certa parte da consulta antes de prosseguir, não funciona e resulta em consulta lenta também:

;with q as (
    select top 100 percent * from bigtable1
    except select * from similar_bigtable2
)
select * from q order by sort_column

Observando os planos de consulta, não é difícil encontrar o motivo:

Plano de consulta Plano de consulta com ORDER BY

O SQL Server coloca dois tipos de 9 milhões de linhas antes do hashmatch, enquanto eu preferiria ter adicionado apenas um tipo de 70.000 linhas após o hashmatch.

Então a pergunta: como instruir o otimizador de consulta para fazer isso?

thomaspaulb
fonte
3
Ele não classifica antes do hashmatch, classifica e faz uma junção de mesclagem (não uma junção de hash). Talvez haja uma dica para forçar uma junção de hash (ou impedir uma junção de mesclagem)?
Thilo
3
Parece que o otimizador de consultas do SQL Server determinou que a classificação dos dados era benéfica para que ele pudesse usar a junção de mesclagem muito mais rápida (que funciona apenas para dados classificados) em vez da junção de correspondência de hash muito mais lenta ou junção de loop aninhado ....
marc_s
9
Você já tentou alternativas para EXCEPT(por exemplo OUTER JOIN)? Sei que a sintaxe é menos conveniente, mas você pode jogar com dicas de índice / junção melhor lá (ou talvez não seja necessário). A alternativa que você está usando agora (primeiro em uma tabela #temp) é uma solução alternativa de último recurso, mas em alguns casos é a única maneira de forçar o otimizador a separar completamente duas partes de uma consulta da maneira que desejar.
Aaron Bertrand

Respostas:

1

A principal diferença entre esses dois planos de consulta está na diferença de Hash Match e Merge Join. A correspondência de hash é mais eficiente e, como você pode ver, a consulta é executada mais rapidamente na opção 1 (não usando o CTE).

O CTE é uma ótima ferramenta, mas parece não ser eficiente em dois casos, Predicados Complexos ou Chave Pai / Filho Não Exclusiva. No seu caso, não há uma chave exclusiva e o SQL Server precisa classificar os conjuntos de dados primeiro para poder atender aos seus requisitos. Dê uma olhada no link abaixo, que informa mais sobre esse assunto: http://blogs.msdn.com/b/sqlcat/archive/2011/04/28/optimize-recursive-cte-query.aspx

Portanto, parece que você precisa aceitar sua lentidão ou reescrever a lógica com o loop WHILE, que pode ser mais eficiente.

Céu
fonte
0

Tente isso, melhor?

select * from
(
    select * from bigtable1
    except 
    select * from similar_bigtable2
) t
order by sort_column
Gordon Bell
fonte
0

Essa não é uma solução ideal, mas se você não conseguir estruturar o tsql para gerar um plano eficiente, poderá definir um guia de plano para forçar o plano que deseja. Fazer isso significaria que, se um plano mais eficiente se tornar disponível, o SQL não o considerará, mas é uma opção.

cfradenburg
fonte