Eu tenho uma consulta muito complexa (quem não tem :-)). Nesta consulta está um JOIN para uma tabela como esta (claro que a original tem mais algumas colunas):
CREATE TABLE dbo.tbl_detail (id bigint PRIMARY key,
descr varchar(300),
txt varchar(MAX),
descr_p varchar(300)
);
Há duas junções para esta tabela na consulta:
LEFT JOIN dbo.tbl_detail td1 ON td1.id = main.detail_id
LEFT JOIN dbo.tbl_detail td2 ON td2.id = main.second_id
- A consulta retorna aproximadamente 200 mil linhas.
- td1 gera todas as colunas
- td2 gera todas as colunas, exceto [txt] (
varchar(max)
)
Problema:
- td2 usa um HASH JOIN
- mas td1 usa um NESTED LOOKUP (mais de 200k linhas; nenhum problema estatístico, pois as estimativas estão corretas)
- quando eu removo a coluna [txt] da saída td1 ela usa um HASH JOIN também
- quando eu altero a coluna para VARCHAR(5000) (ou menor) ela usa um HASH JOIN
- quando eu altero a coluna para VARCHAR(8000) ela usa o NESTED LOOKUP novamente
OPTION (HASH JOIN)
ouLEFT HASH JOIN
funcionaria, mas produz um plano de consulta extremamente lento
Pergunta: Por que nem sempre usa o HASH JOIN? Existe um limite de comprimento (por coluna ou soma de todas as colunas de saída)?
PS: Microsoft SQL Server 2014 (SP2) Developer Edition
É importante lembrar que o otimizador de consulta não escolhe cada junção individualmente e independentemente de todo o resto da consulta. As junções têm propriedades diferentes, o que significa que um tipo de junção diferente pode ser melhor ou pior dependendo de outras junções ou operações no plano. Posso gerar dados de teste que apresentem comportamento semelhante ao que você vê aqui, mas a reprodução depende da memória disponível para o servidor.
Coloque 10k linhas cada em duas tabelas:
Aqui está a consulta para testar:
Se eu adicionar
d.txt_5k
àSELECT
lista, naturalmente recebo uma junção de hash:No entanto, se eu adicionar
d.txt_max
àSELECT
lista, naturalmente recebo uma junção de loop:A chave aqui é que uma junção de loop preserva a ordem da tabela externa. Isso significa que uma junção de loop aninhado pode evitar uma classificação explícita para algumas consultas. As consultas com uma junção de hash podem precisar fazer a classificação. O custo da classificação depende (entre outras coisas) do tamanho estimado dos dados. O otimizador de consulta estima o tamanho dos dados com base no número estimado de linhas e nos tipos de dados. Com a
VARCHAR(5000)
coluna obtenho um tamanho estimado de 27 MB e com aVARCHAR(MAX)
coluna obtenho um tamanho estimado de 42 MB. Na minha máquina, espera-se que a classificação de 42 MB seja derramada no disco, o que torna o plano de junção de hash muito mais caro do que o plano de loop para aVARCHAR(MAX)
coluna.Podemos ver isso mais claramente usando dicas para forçar os diferentes planos para que possamos compará-los. Aqui está a comparação para a
VARCHAR(5000)
consulta:E a comparação para a
VARCHAR(MAX)
consulta:Claro, pode não ser por isso que você está vendo o comportamento que está vendo para sua consulta específica. Eu só queria fornecer um exemplo de por que alterar um tipo de dados pode alterar o tipo de junção. Vou assumir que há um problema causado pela junção de loop e que você realmente precisa que seja uma junção de hash. A
OPTION (HASH JOIN)
dica pode não ser uma boa solução alternativa porque forçará cada junção a ser uma junção de hash na consulta. ALEFT HASH JOIN
dica pode não ser uma boa solução alternativa porque implica em umaFORCE ORDER
dica que significa que o otimizador de consulta não poderá alterar a ordem de junção. Talvez você possa mover a parte problemática da consulta para uma tabela temporária e aplicar as dicas necessárias à consulta menor. É realmente difícil dizer muito sem mais informações.Você também pode tentar uma
FORCESCAN
dica para incentivar a junção de hash, embora eu deteste recomendá-la. Certifique-se de testar cuidadosamente: