Por que a consulta a seguir é lenta?
select count(*)
from [dbo].[mt_dispatch_link]
, [dbo].[_mt_dispatch] [_mt_dispatch]
where (mt_dispatch_link.contract_id_1 = _mt_dispatch.contract_id
and mt_dispatch_link.dispatch_id_1 = _mt_dispatch.dispatch_id)
or (mt_dispatch_link.contract_id_2 = _mt_dispatch.contract_id
and mt_dispatch_link.dispatch_id_2 = _mt_dispatch.dispatch_id)
Demora mais de 10 minutos e, em seguida, costumo pará-lo nesse ponto. Minha pergunta é mais sobre como entender o plano de consulta.
Olhando para o plano de consulta, posso ver que a varredura de índice clusterizado inferior está retornando cerca de 250.000 registros, mas o custo é 0% e está sendo colocado em uma tabela temporária.
A varredura de índice superior é de cerca de 25.000 registros.
Mas o custo de 95% vem da junção aninhada. Que conclusões devo tirar disso?
O plano de consulta acima mostra duas varreduras de índice, isso significa que está fazendo 25.000 + 250.000 varreduras de índice ou significa que está fazendo 25.000 * 250.000 varreduras de índice?
Se eu mudar a consulta para isso (adicionando FORCESEEK
):
select count(*)
from [dbo].[mt_dispatch_link]
, [dbo].[_mt_dispatch] [_mt_dispatch]
WITH (FORCESEEK)
where (mt_dispatch_link.contract_id_1 = _mt_dispatch.contract_id
and mt_dispatch_link.dispatch_id_1 = _mt_dispatch.dispatch_id)
or (mt_dispatch_link.contract_id_2 = _mt_dispatch.contract_id
and mt_dispatch_link.dispatch_id_2 = _mt_dispatch.dispatch_id)
Acabo com um plano muito melhor e a consulta é executada instantaneamente:
Executei estatísticas de atualização em ambas as tabelas. Não corrigiu infelizmente. O design da tabela não é muito bom, então acho que o SQL Server realmente não entende e, portanto, apresenta um plano de consulta ruim. Mais algumas informações sobre o design da tabela em Como otimizar a consulta .
Por que o otimizador de consulta não apresenta o plano ideal?
O otimizador nem sempre considera planos de união de índice (como o mostrado em seu segundo gráfico) para resolver disjunções (
OR
predicados), a menos que uma dicaFORCESEEK
ouINDEX
seja especificada. Esta é uma heurística* baseada em algumas considerações práticas:O uso de uma dica altera a forma como o otimizador pesquisa o espaço de planos possíveis. Desativa algumas das heurísticas gerais e segue uma estratégia mais orientada para objetivos.
O objetivo primário usual do otimizador é encontrar um bom plano rapidamente. Ele não procura exaustivamente o 'melhor' plano (mesmo consultas relativamente simples poderiam levar anos para serem compiladas se assim fosse).
Junções com várias condições separadas por
OR
têm sido problemáticas há muito tempo. Ao longo dos anos, o otimizador adicionou novos truques, como convertê-los emUNION
formas equivalentes, mas as transformações disponíveis são limitadas, por isso é muito fácil se soltar.No que diz respeito ao plano de consulta:
OR
predicado completoSe houver 25.000 linhas na tabela Dispatch Link, o spool será totalmente verificado 25.000 vezes. É claro que isso é um desastre (e sem interseção de índice, o melhor que o otimizador pode fazer é executar tudo em vários encadeamentos).
Os custos percentuais nos planos de consulta são apenas estimativas do otimizador . Eles nunca refletem os custos reais de execução e estão sujeitos ao modelo do otimizador e geralmente têm pouca semelhança com o custo 'verdadeiro' de execução do plano em seu hardware específico.
Os números de custeio existem para serem informativos, mas não devem ser interpretados literalmente. O modelo específico que o otimizador usa produz planos muito bons para a maioria das consultas na maioria dos sistemas em todo o mundo - isso não significa que o modelo se aproxime da realidade de alguém, apenas funciona razoavelmente bem na prática.
Alterar o design para que os pares (Despacho, Contrato) sejam armazenados em linhas, em vez de repetidos nas colunas, fará com que todo o problema de interseção de índice desapareça. Projetos relacionais com restrições e índices úteis quase sempre tiram o melhor proveito do otimizador.
* Isso pode ser substituído pelo sinalizador de rastreamento não documentado 8726