Eu tenho uma consulta SQL que passei nos últimos dois dias tentando otimizar usando tentativa e erro e o plano de execução, mas sem sucesso. Por favor, perdoe-me por fazer isso, mas postarei todo o plano de execução aqui. Fiz um esforço para tornar os nomes de tabela e coluna no plano de consulta e execução genéricos, por questões de brevidade e para proteger o IP da minha empresa. O plano de execução pode ser aberto com o SQL Sentry Plan Explorer .
Fiz uma boa quantidade de T-SQL, mas usar planos de execução para otimizar minha consulta é uma nova área para mim e realmente tentei entender como fazê-lo. Portanto, se alguém pudesse me ajudar com isso e explicar como esse plano de execução pode ser decifrado para encontrar maneiras na consulta de otimizá-lo, ficaria eternamente grato. Tenho muito mais consultas para otimizar - só preciso de um trampolim para me ajudar com essa primeira.
Esta é a consulta:
DECLARE @Param0 DATETIME = '2013-07-29';
DECLARE @Param1 INT = CONVERT(INT, CONVERT(VARCHAR, @Param0, 112))
DECLARE @Param2 VARCHAR(50) = 'ABC';
DECLARE @Param3 VARCHAR(100) = 'DEF';
DECLARE @Param4 VARCHAR(50) = 'XYZ';
DECLARE @Param5 VARCHAR(100) = NULL;
DECLARE @Param6 VARCHAR(50) = 'Text3';
SET NOCOUNT ON
DECLARE @MyTableVar TABLE
(
B_Var1_PK int,
Job_Var1 varchar(512),
Job_Var2 varchar(50)
)
INSERT INTO @MyTableVar (B_Var1_PK, Job_Var1, Job_Var2)
SELECT B_Var1_PK, Job_Var1, Job_Var2 FROM [fn_GetJobs] (@Param1, @Param2, @Param3, @Param4, @Param6);
CREATE TABLE #TempTable
(
TTVar1_PK INT PRIMARY KEY,
TTVar2_LK VARCHAR(100),
TTVar3_LK VARCHAR(50),
TTVar4_LK INT,
TTVar5 VARCHAR(20)
);
INSERT INTO #TempTable
SELECT DISTINCT
T.T1_PK,
T.T1_Var1_LK,
T.T1_Var2_LK,
MAX(T.T1_Var3_LK),
T.T1_Var4_LK
FROM
MyTable1 T
INNER JOIN feeds.MyTable2 A ON A.T2_Var1 = T.T1_Var4_LK
INNER JOIN @MyTableVar B ON B.Job_Var2 = A.T2_Var2 AND B.Job_Var1 = A.T2_Var3
GROUP BY T.T1_PK, T.T1_Var1_LK, T.T1_Var2_LK, T.T1_Var4_LK
-- This is the slow statement...
SELECT
CASE E.E_Var1_LK
WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
WHEN 'Text3' THEN T.TTVar2_LK
END,
T.TTVar4_LK,
T.TTVar3_LK,
CASE E.E_Var1_LK
WHEN 'Text1' THEN F.F_Var1
WHEN 'Text2' THEN F.F_Var2
WHEN 'Text3' THEN T.TTVar5
END,
A.A_Var3_FK_LK,
C.C_Var1_PK,
SUM(CONVERT(DECIMAL(18,4), A.A_Var1) + CONVERT(DECIMAL(18,4), A.A_Var2))
FROM #TempTable T
INNER JOIN TableA (NOLOCK) A ON A.A_Var4_FK_LK = T.TTVar1_PK
INNER JOIN @MyTableVar B ON B.B_Var1_PK = A.Job
INNER JOIN TableC (NOLOCK) C ON C.C_Var2_PK = A.A_Var5_FK_LK
INNER JOIN TableD (NOLOCK) D ON D.D_Var1_PK = A.A_Var6_FK_LK
INNER JOIN TableE (NOLOCK) E ON E.E_Var1_PK = A.A_Var7_FK_LK
LEFT OUTER JOIN feeds.TableF (NOLOCK) F ON F.F_Var1 = T.TTVar5
WHERE A.A_Var8_FK_LK = @Param1
GROUP BY
CASE E.E_Var1_LK
WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
WHEN 'Text3' THEN T.TTVar2_LK
END,
T.TTVar4_LK,
T.TTVar3_LK,
CASE E.E_Var1_LK
WHEN 'Text1' THEN F.F_Var1
WHEN 'Text2' THEN F.F_Var2
WHEN 'Text3' THEN T.TTVar5
END,
A.A_Var3_FK_LK,
C.C_Var1_PK
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
DROP TABLE #TempTable
END
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
DROP TABLE #TempTable
END
O que descobri é que a terceira declaração (comentada como lenta) é a parte que está demorando mais tempo. As duas declarações anteriores retornam quase instantaneamente.
O plano de execução está disponível como XML neste link .
É melhor clicar com o botão direito do mouse e salvar e abrir no SQL Sentry Plan Explorer ou em algum outro software de visualização, em vez de abrir no navegador.
Se precisar de mais informações minhas sobre tabelas ou dados, não hesite em perguntar.
tempdb
. ou seja, as estimativas para as linhas resultantes da junção entreTableA
e@MyTableVar
estão muito distantes. Além disso, o número de linhas entrando nas classificações é muito maior do que o estimado, portanto elas também podem estar se espalhando.Respostas:
Antes de chegar à resposta principal, existem dois softwares que você precisa atualizar.
Atualizações de software necessárias
O primeiro é o SQL Server. Você está executando o SQL Server 2008 Service Pack 1 (compilação 2531). Você deve estar atualizado até pelo menos o Service Pack atual (SQL Server 2008 Service Pack 3 - build 5500). A versão mais recente do SQL Server 2008 no momento da redação deste artigo é o Service Pack 3, Atualização Cumulativa 12 (versão 5844).
O segundo software é o SQL Sentry Plan Explorer . As versões mais recentes têm novos recursos e correções significativos, incluindo a capacidade de fazer upload diretamente de um plano de consulta para análise especializada (não é necessário colar XML em qualquer lugar!)
Análise do plano de consulta
A estimativa de cardinalidade para a variável da tabela está exatamente correta, graças a uma recompilação no nível da instrução:
Infelizmente, as variáveis de tabela não mantêm estatísticas de distribuição; portanto, o otimizador sabe que existem seis linhas; não conhece nada dos valores que possam estar nessas seis linhas. Essa informação é crucial, pois a próxima operação é uma junção a outra tabela. A estimativa de cardinalidade dessa associação é baseada em um palpite do otimizador:
A partir daí, o plano escolhido pelo otimizador é baseado em informações incorretas, portanto, não é de admirar que o desempenho seja tão ruim. Em particular, a memória reservada para classificações e tabelas de hash para junções de hash será muito pequena. No tempo de execução, as operações de classificação e hash transbordantes serão derramadas para disco tempdb físico .
O SQL Server 2008 não destaca isso nos planos de execução; você pode monitorar os derramamentos usando Eventos estendidos ou Avisos de classificação do criador de perfil e Avisos de hash . A memória é reservada para classificações e hashes com base em estimativas de cardinalidade antes do início da execução e não pode ser aumentada durante a execução, independentemente da quantidade de memória disponível que o SQL Server possa ter. Portanto, estimativas precisas de contagem de linhas são cruciais para qualquer plano de execução que envolva operações que consomem memória do espaço de trabalho.
Sua consulta também é parametrizada. Você deve adicionar
OPTION (RECOMPILE)
à consulta se valores diferentes de parâmetros afetarem o plano de consulta. Você provavelmente deve considerar usá-lo de qualquer maneira, para que o otimizador possa ver o valor@Param1
no momento da compilação. Se nada mais, isso pode ajudar o otimizador a produzir uma estimativa mais razoável para a busca de índice mostrada acima, dado que a tabela é muito grande e particionada. Também pode permitir a eliminação de partição estática.Tente a consulta novamente com uma tabela temporária em vez da variável de tabela e
OPTION (RECOMPILE)
. Você também deve tentar materializar o resultado da primeira junção em outra tabela temporária e executar o restante da consulta. O número de linhas não é tão grande (3.285.620); portanto, isso deve ser razoavelmente rápido. O otimizador terá uma estimativa exata da cardinalidade e estatísticas de distribuição para o resultado da associação. Com sorte, o restante do plano se encaixará bem.Trabalhando a partir das propriedades mostradas no plano, a consulta de materialização seria:
Você também pode
INSERT
inserir uma tabela temporária predefinida (os tipos de dados corretos não são mostrados no plano, portanto não posso fazer essa parte). A nova tabela temporária pode ou não se beneficiar de índices clusterizados e não clusterizados.fonte
#AnotherTempTable
. Isso pareceu ter o melhor impacto - as outras sugestões (o uso de uma tabela temporária em vez de uma variável de tabela para @MyTableVar e o usoOPTION (RECOMPILE)
não tiveram muito efeito ou nenhum efeito. O 'Anonymize' e 'Post to SQLPerformance.com' as opções no SQL Sentry Plan Explorer são ótimas - eu apenas as usei: answers.sqlperformance.com/questions/1087Percebo que deve haver um PK em @MyTableVar e concordo que #MyTableVar geralmente tem melhor desempenho (principalmente com um número maior de linhas).
A condição na cláusula where
deve ser movido para a junção interna A AND'ed. O otimizador não é inteligente o suficiente na minha experiência para fazer isso (desculpe, não olhei para o plano) e pode fazer uma enorme diferença.
Se essas alterações não mostrarem melhoria, em seguida, criaria outra tabela temporária de A e todas as coisas às quais ela se restringe (agradavelmente?) Por A.A_Var8_FK_LK = @ Param1, se esse agrupamento fizer sentido lógico para você.
Em seguida, crie um índice em cluster nessa tabela temporária (antes ou depois da criação) para a próxima condição de associação.
Em seguida, junte esse resultado às poucas tabelas (F e T) que restam.
Bam, que precisa de um plano de consulta fedorento quando as estimativas de linha estão desativadas e às vezes não é facilmente improvável de qualquer maneira). Suponho que você tenha índices adequados, que é o que eu verificaria primeiro dentro do plano.
Um rastreamento pode mostrar os derramamentos de tempdb que podem ou não ter um impacto drástico.
Outra abordagem alternativa - que é mais rápida de tentar, pelo menos - é ordenar as tabelas do menor número de linhas (A) para o mais alto e começar a adicionar a mesclagem, o hash e o loop nas junções. Quando dicas estão presentes, a ordem de junção é fixada conforme especificado. Outros usuários evitam sabiamente essa abordagem porque ela pode prejudicar a longo prazo se a contagem relativa de linhas mudar drasticamente. Um número mínimo de dicas é desejável.
Se você estiver fazendo muitas dessas ações, talvez um otimizador comercial valha a pena tentar (ou testar) e ainda assim ser uma boa experiência de aprendizado.
fonte