Criei a seguinte consulta do SQL Server, mas ela está encontrando o defeito de junção anti-semi no SQL Server 2005, que resulta em estimativas de cardinalidade imprecisas (1 -- urgh!) e é executado para sempre. Como é um SQL Server de produção de longa data, não posso sugerir facilmente a atualização de versões e, como tal, não posso forçar a dica traceflag 4199 nessa consulta específica.
Estou tendo dificuldade em refatorar o arquivo WHERE AND NOT IN (SELECT)
. Alguém pode se importar em ajudar? Certifiquei-me de tentar usar as melhores junções com base em pares de chaves em cluster.
SELECT TOP 5000 d.doc2_id
,d.direction_cd
,a.address_type_cd
,d.external_identification
,s.hash_value
,d.publishdate
,d.sender_address_id AS [D2 Sender_Address_id]
,a.address_id AS [A Address_ID]
,d.message_size
,d.subject
,emi.employee_id
FROM assentor.emcsdbuser.doc2 d(NOLOCK)
INNER JOIN assentor.emcsdbuser.employee_msg_index emi(NOLOCK)
ON d.processdate = emi.processdate
AND d.doc2_id = emi.doc2_id
INNER LOOP JOIN assentor.emcsdbuser.doc2_address a(NOLOCK)
ON emi.doc2_id = a.doc2_id
AND emi.address_type_cd = a.address_type_cd
AND emi.address_id = a.address_id
INNER JOIN sis.dbo.sis s(NOLOCK) ON d.external_identification = s.external_identification
WHERE d.publishdate > '2008-01-01'
**AND d.doc2_id NOT IN (
SELECT doc2_id
FROM assentor.emcsdbuser.doc2_address d2a(NOLOCK)
WHERE d.doc2_id = d2a.doc2_id
AND d2a.address_type_cd = 'FRM'
)**
OPTION (FAST 10)
Observe que a Employee_MSG_Index
tabela tem 500m de linhas, doc2
1,5b de linhas, SIS
~500m de linhas.
Qualquer ajuda seria apreciada!
O bug de estimativa de cardinalidade anti-semijunção pode ser reproduzido em todas as versões do SQL Server de 2005 a 2012 inclusive . Todos exigem o sinalizador de rastreamento 4199 para ativar a correção, portanto, a atualização não resolveria seu problema sem ativar o 4199 (embora haja muitos outros bons motivos para atualizar a partir de 2005, é claro).
Se apenas uma consulta específica for afetada, você poderá usar
OPTION (QUERYTRACEON 4199)
para habilitar o sinalizador de rastreamento apenas para essa consulta. Esta dica de consulta é documentada e tem suporte para uso com 4199 e se aplica a partir do SQL Server 2005 Service Pack 2 em diante.Essa dica é executada
DBCC TRACEON (4199)
de maneira eficazDBCC TRACEOFF (4199)
em torno da consulta e, como resultado, requer permissão de administrador do sistema. Se isso for um problema, adicione a dica usando um guia de plano .Você também deve testar todo o seu sistema com 4199 ativado em toda a instância . As regressões do plano são possíveis, mas no geral você pode achar que as várias correções do otimizador habilitadas por esse sinalizador valem a pena. Todas as correções futuras do processador de consulta que afetam o plano exigem que esse sinalizador seja ativado.
Tudo o que disse...
Conforme mencionado na resposta do ypercube , o bug requer duas ou mais colunas de junção para se manifestar (entre muitos detalhes). A redundância em sua
NOT IN
cláusula faz com que o otimizador veja duas comparações de colunas (embora logicamente haja apenas uma), expondo assim o bug.A remoção dessa redundância 'resolverá' o problema dessa consulta específica, embora outras consultas que realmente tenham mais de um predicado de junção ainda sejam vulneráveis .
Exemplo
Para ilustrar, aqui está um exemplo baseado na postagem do blog CSS vinculada na pergunta (mas com um script completo!):
Dados de amostra:
Consulta de teste usando
NOT IN
com predicado redundante:O plano de execução estimado mostra uma estimativa de 1 linha após a anti-semi join:
Nota lateral: Na verdade, este é um exemplo de outro bug (raro). Escrever a
WHERE
cláusula comot1.c1 = t2.c1
em vez det2.c1 = t1.c1
permite que o otimizador veja que os dois predicados de junção são de fato os mesmos e o bug não se manifesta.A mesma consulta com
OPTION (QUERYTRACEON 4199)
:O plano de execução estimado agora mostra uma estimativa de 2.046 linhas , o que é exatamente correto:
Também podemos remover o predicado redundante:
O plano de execução usa uma otimização adicional não relacionada (o Stream Aggregate), mas o ponto importante é que a estimativa pós-junção esteja correta sem ter que habilitar 4199:
Múltiplas colunas anti-semi-junção
É possível expressar uma anti-semi join em várias colunas usando a
NOT IN
sintaxe. Esses casos exigirão 4199. Por exemplo, a próxima consulta unec1
ec2
:O plano de execução mostra a estimativa incorreta de 1 linha:
Com 4199, o problema é resolvido:
Outras sintaxes
É melhor evitar o uso
NOT IN
dessa maneira, principalmente pelos motivos mencionados nos livros on-line:Esse problema foi escrito
NOT IN
muitas vezes. Existem muitas sintaxes alternativas disponíveis, das quais é minha preferência pessoal. Observe que alterar a sintaxe não evitará o erro de estimativa de cardinalidade:NULLs
NOT EXISTS
Essa união anti-semi de duas colunas produz a estimativa de 1 linha e requer 4199 para corrigi-la. Os planos de execução são exatamente os mesmos vistos anteriormente, portanto não os repetirei. A
NOT EXISTS
sintaxe evita oNULLs
problema comNOT IN
.Outras observações
Concordo com as outras observações de ypercube.
Espalhar
NOLOCK
dicas sobre cada tabela em uma consulta é um mau cheiro de código. Se a consulta realmente tolerarREAD UNCOMMITTED
a semântica da transação, defina o nível de isolamento explicitamente.TOP
semORDER BY
é outro sinal de código ruim.TOP
requer umaORDER BY
cláusula para definir o queTOP
significa. Nunca confie no comportamento observado, use um nível superior explícitoORDER BY
para obter uma garantia.INNER LOOP JOIN
e dicas de junção em geral, implicam umaFORCE ORDER
dica de consulta. Isso limita severamente a liberdade do otimizador e geralmente é mal compreendido e mal aplicado. Nunca use dicas que você não entende completamente.O link que você forneceu diz que o bug afeta apenas junções com mais de uma coluna:
E não consigo entender por que você escreveu
NOT IN
dessa maneira (adicionando ad.doc2_id = d2a.doc2_id
condição na subconsulta). É redundante (a menos que as colunas unidas sejam anuláveis - são?), Então você pode escrever oNOT IN
como:ou com
NOT EXISTS
:Experimente ambos e verifique se o problema de estimativa de cardinalidade foi resolvido.
Outras notas:
address_type_cd
?NOLOCK
?TOP
semORDER BY
pode fornecer resultados diferentes por execução.