Eu tenho uma consulta bastante simples
SELECT TOP 1 dc.DOCUMENT_ID,
dc.COPIES,
dc.REQUESTOR,
dc.D_ID,
cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
Isso está me dando um desempenho horrível (como nunca se incomodou em esperar que terminasse). O plano de consulta fica assim:
No entanto, se eu remover TOP 1
, recebo um plano parecido com este e é executado em 1-2 segundos:
Corrija PK e indexação abaixo.
O fato de o TOP 1
plano de consulta alterado não me surpreender, estou um pouco surpreso que isso o torne muito pior.
Nota: Li os resultados deste post e entendi o conceito de Row Goal
etc. O que me interessa é como posso alterar a consulta para que ela use o melhor plano. Atualmente, estou despejando os dados em uma tabela temporária e retirando a primeira linha dela. Eu estou querendo saber se existe um método melhor.
Editar Para as pessoas que leem isso após o fato, aqui estão algumas informações extras.
- Document_Queue - PK / CI é D_ID e possui ~ 5k linhas.
- Correspondence_Journal - PK / CI é FILE_NUMBER, CORRESPONDENCE_ID e possui ~ 1,4 mil linhas.
Quando comecei, não havia outros índices. Acabei com um em Correspondence_Journal (Document_Id, File_Number)
fonte
DOCUMENT_ID
relacionamento entre as duas tabelas (ou cada registroCORRESPONDENCE_JOURNAL
possui um registro correspondenteDOCUMENT_QUEUE
)?Respostas:
Tente forçar uma junção de hash *
O otimizador provavelmente pensou que um loop seria melhor com o top 1 e isso faz sentido, mas, na realidade, não funcionou aqui. Apenas um palpite aqui, mas talvez o custo estimado desse spool esteja desligado - ele usa TEMPDB - você pode ter um TEMPDB com desempenho ruim.
* Tenha cuidado com as dicas de junção , porque elas forçam a ordem de acesso à tabela de plano para corresponder à ordem escrita das tabelas na consulta (como se
OPTION (FORCE ORDER)
tivesse sido especificada). No link da documentação:Isso pode não produzir efeitos indesejáveis no exemplo, mas, em geral, pode muito bem.
FORCE ORDER
(implícita ou explícita) é uma dica muito poderosa que vai além da imposição da ordem; impede que uma ampla variedade de técnicas de otimizador seja aplicada, incluindo agregações parciais e reordenação.Uma dica de
OPTION (HASH JOIN)
consulta pode ser menos intrusiva em casos adequados, pois isso não implicaFORCE ORDER
. No entanto, aplica-se a todas as junções na consulta. Outras soluções estão disponíveis.fonte
Como você obtém o plano correto com o
ORDER BY
, talvez você possa simplesmente rolar seu próprioTOP
operador?Na minha opinião, o plano de consulta
ROW_NUMBER()
acima deve ser o mesmo que se você tivesse umORDER BY
. O plano de consulta agora deve ter um segmento, projeto de sequência e, finalmente, um operador de filtro, o restante deve parecer exatamente como o seu bom plano.fonte
Edit: +1 funciona nessa situação, porque
FILE_NUMBER
é uma versão de string preenchida com zero de um número inteiro. Uma solução melhor aqui para seqüências de caracteres é anexar''
(a sequência vazia), pois a adição de um valor pode afetar a ordem ou os números adicionarem algo que é uma constante, mas contém uma função não determinística, comosign(rand()+1)
. A idéia de 'quebrar o tipo' ainda é válida aqui, mas meu método não era ideal.+1
Não, não quero dizer que estou de acordo com nada, quero dizer isso como uma solução. Se você alterar sua consulta para
ORDER BY cj.FILE_NUMBER + 1
, oTOP 1
comportamento será diferente.Veja que, com a pequena meta de linha em vigor para uma consulta ordenada, o sistema tentará consumir os dados em ordem, para evitar ter um operador de classificação. Ele também evitará criar uma tabela de hash, imaginando que provavelmente não precisa fazer muito trabalho para encontrar a primeira linha. No seu caso, isso está errado - pela espessura dessas setas, parece que é preciso consumir muitos dados para encontrar uma única correspondência.
A espessura dessas setas sugere que a
DOCUMENT_QUEUE
tabela (DQ) é muito menor que aCORRESPONDENCE_JOURNAL
tabela (CJ). E que o melhor plano seria realmente verificar as linhas DQ até que uma linha CJ seja encontrada. Na verdade, é isso que o Query Optimizer (QO) faria se não tivesse esse problemaORDER BY
lá dentro, muito bem suportado por um índice de cobertura no CJ.Portanto, se você descartou
ORDER BY
completamente, espero que você obtenha um plano que envolva um loop aninhado, iterando pelas linhas no DQ, procurando no CJ para garantir que a linha exista. E comTOP 1
isso isso parava depois que uma única linha era puxada.Mas se você realmente precisar da primeira linha em
FILE_NUMBER
ordem, poderá enganar o sistema para ignorar o índice que parece (incorretamente) ser útil, fazendoORDER BY CJ.FILE_NUMBER+1
- o que sabemos que manterá a mesma ordem de antes, mas principalmente o QO não. O QO se concentrará em obter o conjunto completo, para que um operador Top N Sort possa ser satisfeito. Esse método deve produzir um plano que contenha um operador Compute Scalar para calcular o valor do pedido e um operador Top N Sort para obter a primeira linha. Mas, à direita, você deve ver um Nested Loop agradável, fazendo muitas pesquisas no CJ. E melhor desempenho do que executar uma grande tabela de linhas que não corresponde a nada no DQ.O Hash Match não é necessariamente horrível, mas se o conjunto de linhas que você está retornando do DQ é muito menor que o CJ (como eu esperava que fosse), o Hash Match analisará muito mais o CJ do que precisa.
Nota: usei +1 em vez de +0 porque é provável que o otimizador de consultas reconheça que +0 não altera nada. Obviamente, o mesmo pode ser aplicado ao +1, se não agora, em algum momento no futuro.
fonte
A adição
OPTION (QUERYTRACEON 4138)
desativa o efeito das metas de linha apenas para essa consulta, sem ser excessivamente prescritiva sobre o plano final e provavelmente será a maneira mais simples / direta.Se a adição dessa dica der um erro de permissão (necessário
DBCC TRACEON
), você poderá aplicá-la usando um guia de plano:Usando
QUERYTRACEON
nos guias de plano de spaghettidba... ou apenas use um procedimento armazenado:
Quais permissões são
QUERYTRACEON
necessárias? por Kendra Littlefonte
As versões mais recentes do SQL Server oferecem opções diferentes (e sem dúvida melhores) para lidar com consultas que obtêm desempenho abaixo do ideal quando o otimizador é capaz de aplicar otimizações de meta de linha. O SQL Server 2016 SP1 apresentou o
DISABLE_OPTIMIZER_ROWGOAL USE HINT
que tem o mesmo efeito que o sinalizador de rastreamento 4138. Se você não estiver nessa versão, também poderá usar aOPTIMIZE FOR
dica de consulta para obter um plano de consulta projetado para retornar todas as linhas em vez de apenas 1. A consulta abaixo retornará os mesmos resultados que o da pergunta, mas não será criado com o objetivo de obter apenas 1 linha.fonte
Como você está fazendo um
TOP(1)
, eu recomendo fazer oORDER BY
determinístico para começar. No mínimo, isso garantirá que os resultados sejam funcionalmente previsíveis (sempre úteis para testes de regressão). Parece que você precisa adicionarDC.D_ID
eCJ.CORRESPONDENCE_ID
para isso.Ao examinar os planos de consulta, às vezes acho instrutivo simplificar a consulta: Possivelmente selecione todas as linhas dc relevantes em uma tabela temporária com antecedência, para eliminar problemas com a estimativa de cardinalidade em
QUEUE_DATE
ePRINT_LOCATION
. Isso deve ser rápido, considerando o número de linhas baixo. Você pode adicionar índices a essa tabela temporária, se necessário, sem alterar a tabela permanente.fonte