Como medir ou encontrar o custo de criar um plano de consulta?


18

Eu tenho um caso típico em que o sniffing de parâmetros faz com que um plano de execução "ruim" chegue ao cache do plano, fazendo com que as execuções subseqüentes do meu procedimento armazenado sejam muito lentas. Eu posso "resolver" esse problema com variáveis ​​locais,, OPTIMIZE FOR ... UNKNOWNe OPTION(RECOMPILE). No entanto, também posso mergulhar na consulta e tentar otimizá-la.

Estou tentando determinar se devo : com tempo limitado para corrigir problemas, gostaria de saber o custo de não fazer isso. A meu ver, se eu continuar OPTION(RECOMPILE), o efeito líquido é que um plano de consulta é recriado toda vez que a consulta é executada. Então, acho que preciso saber:

Como descobrir qual é o custo da criação de um plano de consulta?

Para responder minha própria pergunta, pesquisei no Google (por exemplo, com esta consulta ) e examinei a documentação das colunas da dm_exec_query_statsDMV . Também examinei a janela de saída no SSMS para "Plano de consulta real" para encontrar essas informações. Finalmente, eu tenho procurado DBA.SE . Nenhum deles levou a uma resposta.

Alguém pode me dizer? É possível encontrar ou medir o tempo necessário para a criação do plano?


5
Eu recomendaria pegar uma cópia do Inside the SQL Server Query Optimizer de Benjamin Nevarez . É grátis. O capítulo 5 'O processo de otimização' pode ajudá-lo a calcular o tempo de compilação para sua consulta. No mínimo, é informativo sobre o que o otimizador passa para criar um plano de consulta.
Mark Sinkinson

Respostas:


18

Como descobrir qual é o custo da criação de um plano de consulta?

Você pode examinar as propriedades do nó raiz no plano de consulta, por exemplo:

Extrato de propriedades raiz
(captura de tela do Sentry One Plan Explorer gratuito )

Essas informações também estão disponíveis consultando o cache do plano, por exemplo, usando uma consulta baseada nos seguintes relacionamentos:

WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT 
    CompileTime = c.value('(QueryPlan/@CompileTime)[1]', 'int'),
    CompileCPU = c.value('(QueryPlan/@CompileCPU)[1]', 'int'),
    CompileMemory = c.value('(QueryPlan/@CompileMemory)[1]', 'int'),
    ST.[text],
    QP.query_plan
FROM sys.dm_exec_cached_plans AS CP
CROSS APPLY sys.dm_exec_query_plan(CP.plan_handle) AS QP
CROSS APPLY sys.dm_exec_sql_text(CP.plan_handle) AS ST
CROSS APPLY QP.query_plan.nodes('ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple') AS N(c);

Fragmento de resultados

Para um tratamento completo das opções que você tem para lidar com esse tipo de consulta, consulte o artigo recentemente atualizado de Erland Sommarskog .


4

Assumindo que "custo" é em termos de tempo (embora não tenha certeza do que mais poderia ser em termos de ;-), pelo menos você deve ter uma noção disso fazendo algo como o seguinte:

DBCC FREEPROCCACHE WITH NO_INFOMSGS;

SET STATISTICS TIME ON;

EXEC sp_help 'sys.databases'; -- replace with your proc

SET STATISTICS TIME OFF;

O primeiro item relatado na guia "Mensagens" deve ser:

Tempo de análise e compilação do SQL Server:

Eu executaria isso pelo menos 10 vezes e calcularia a média dos milissegundos "CPU" e "Elapsed".

Idealmente, você executaria isso no Production para obter uma estimativa de tempo real, mas raramente as pessoas têm permissão para limpar o cache do plano no Production. Felizmente, a partir do SQL Server 2008, foi possível limpar um plano específico do cache. Nesse caso, você pode fazer o seguinte:

DECLARE @SQL NVARCHAR(MAX) = '';
;WITH cte AS
(
  SELECT DISTINCT stat.plan_handle
  FROM sys.dm_exec_query_stats stat
  CROSS APPLY sys.dm_exec_text_query_plan(stat.plan_handle, 0, -1) qplan
  WHERE qplan.query_plan LIKE N'%sp[_]help%' -- replace "sp[_]help" with proc name
)
SELECT @SQL += N'DBCC FREEPROCCACHE ('
               + CONVERT(NVARCHAR(130), cte.plan_handle, 1)
               + N');'
               + NCHAR(13) + NCHAR(10)
FROM cte;
PRINT @SQL;
EXEC (@SQL);

SET STATISTICS TIME ON;

EXEC sp_help 'sys.databases' -- replace with your proc

SET STATISTICS TIME OFF;

No entanto, dependendo da variabilidade dos valores transmitidos para o (s) parâmetro (s) que causam o plano em cache "ruim", há outro método a considerar que é o meio termo entre OPTION(RECOMPILE)e OPTION(OPTIMIZE FOR UNKNOWN): Dynamic SQL. Sim, eu disse. E eu até quero dizer SQL dinâmico não parametrizado. Aqui está o porquê.

Você tem claramente dados que têm uma distribuição desigual, pelo menos em termos de um ou mais valores de parâmetros de entrada. As desvantagens das opções mencionadas são:

  • OPTION(RECOMPILE)irá gerar um plano para cada execução e você nunca poderá se beneficiar de qualquer reutilização do plano, mesmo que os valores dos parâmetros passados ​​novamente sejam idênticos às execuções anteriores. Para procs que são chamados com freqüência - uma vez a cada poucos segundos ou mais frequentemente - isso o salvará de uma situação horrível ocasional, mas ainda o deixará em uma situação sempre não tão boa.

  • OPTION(OPTIMIZE FOR (@Param = value)) irá gerar um plano com base nesse valor específico, o que pode ajudar vários casos, mas ainda deixa você aberto ao problema atual.

  • OPTION(OPTIMIZE FOR UNKNOWN)gerará um plano com base no que equivale a uma distribuição média, o que ajudará algumas consultas, mas prejudicará outras. Deve ser o mesmo que a opção de usar variáveis ​​locais.

O SQL dinâmico, no entanto, quando feito corretamente , permitirá que os vários valores passados ​​tenham seus próprios planos de consulta separados, ideais (bem, tanto quanto serão). O principal custo aqui é que, à medida que a variedade de valores transmitidos aumenta, o número de planos de execução no cache aumenta e eles consomem memória. Os custos menores são:

  • Necessidade de Validar Parâmetros de Cadeia de Caracteres para Evitar Injeções de SQL

  • possivelmente precisando configurar um certificado e um usuário baseado em certificado para manter a abstração de segurança ideal, pois o SQL dinâmico requer permissões diretas de tabela.

Então, aqui está como eu gerenciei essa situação quando tive procs chamados mais de uma vez por segundo e atingindo várias tabelas, cada uma com milhões de linhas. Eu tentei, OPTION(RECOMPILE)mas isso provou ser muito prejudicial para o processo nos 99% dos casos que não tinham o parâmetro sniffing / bad cache plan problem. E lembre-se de que um desses procs tinha cerca de 15 consultas e apenas 3 - 5 delas foram convertidas em Dynamic SQL, conforme descrito aqui; O SQL dinâmico não foi usado, a menos que fosse necessário para uma consulta específica.

  1. Se houver vários parâmetros de entrada para o procedimento armazenado, descubra quais são usados ​​com colunas com distribuições de dados altamente díspares (e, portanto, causam esse problema) e quais são usados ​​com colunas com distribuições mais uniformes (e não devem ser causando esse problema).

  2. Crie a cadeia de caracteres Dynamic SQL usando parâmetros para os parâmetros de entrada proc associados a colunas distribuídas uniformemente. Essa parametrização ajuda a reduzir o aumento resultante nos planos de execução no cache relacionado a esta consulta.

  3. Para os parâmetros restantes associados a distribuições altamente variadas, esses devem ser concatenados no SQL dinâmico como valores literais. Como uma consulta exclusiva é determinada por quaisquer alterações no texto da consulta, ter WHERE StatusID = 1é uma consulta diferente e, portanto, um plano de consulta diferente do que ter WHERE StatusID = 2.

  4. Se algum dos parâmetros de entrada proc que devem ser concatenados no texto da consulta forem cadeias, eles deverão ser validados para se proteger contra a injeção SQL (embora isso seja menos provável de acontecer se as cadeias transmitidas forem geradas pelo aplicativo e não um usuário, mas ainda). Faça pelo menos isso REPLACE(@Param, '''', '''''')para garantir que as aspas simples se tornem aspas simples.

  5. Se necessário, crie um certificado que será usado para criar um usuário e assine o procedimento armazenado, de modo que as permissões diretas da tabela sejam concedidas apenas ao novo usuário baseado em certificado e não para [public]ou para usuários que não deveriam ter essas permissões. .

Exemplo proc:

CREATE PROCEDURE MySchema.MyProc
(
  @Param1 INT,
  @Param2 DATETIME,
  @Param3 NVARCHAR(50)
)
AS
SET NOCOUNT ON;

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'
     SELECT  tab.Field1, tab.Field2, ...
     FROM    MySchema.SomeTable tab
     WHERE   tab.Field3 = @P1
     AND     tab.Field8 >= CONVERT(DATETIME, ''' +
  CONVERT(NVARCHAR(50), @Param2, 121) +
  N''')
     AND     tab.Field2 LIKE N''' +
  REPLACE(@Param3, N'''', N'''''') +
  N'%'';';

EXEC sp_executesql
     @SQL,
     N'@P1 INT',
     @P1 = @Param1;

Obrigado por levar (bastante) tempo para responder! No entanto, sou um pouco cético sobre o primeiro passo para obter o tempo de compilação, já que é um fator 3 menor que o resultado obtido pela abordagem de @ PaulWhite . - O segundo em pouco dinâmico SQL é interessante (embora também exigiria tempo para implementar, pelo menos mais do que apenas batendo um OPTIONno meu query), e não iria me machucar muito muito desde esta sproc é bem aproveitado em testes de integração. - De qualquer forma: obrigado por suas idéias!
Jeroen
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.