Estatísticas de espera RESOURCE_SEMAPHORE_QUERY_COMPILE intermitentes


8

Estou tentando solucionar alguns picos de CPU intermitentes que estamos testemunhando em um de nossos servidores SQL de produção. Estamos executando o SQL Server 2008 R2 Standard Edition com 28 GB de RAM e 4 núcleos de CPU. Quando isso acontece, percebemos um grande número de esperas RESOURCE_SEMAPHORE_QUERY_COMPILER, que duram cerca de um minuto ou dois e depois param, e então o uso da CPU volta ao normal.

Depois de pesquisar isso, entendo que isso normalmente é causado pela compilação de muitos planos de execução não reutilizáveis, nos quais estamos trabalhando atualmente nas alterações de nosso aplicativo.

Esse comportamento também pode ser desencadeado por despejos de cache planejados devido à pressão da memória? Se sim, como eu verificaria isso? Estou tentando ver se há algum remédio de curto prazo que possamos fazer, como atualizar a RAM do servidor, até implantarmos as correções de aplicativos. A única outra opção de curto prazo que posso pensar é mover alguns de nossos bancos de dados mais movimentados para um servidor diferente.

Respostas:


6

Acredito que você verá esse sintoma se tiver muitos planos de consulta grandes que estão lutando pela memória para compilar (isso tem muito pouco a ver com a execução da consulta). Para entender isso, suspeito que você esteja usando um ORM ou algum tipo de aplicativo que gere muitas consultas exclusivas, mas relativamente complexas. O SQL Server pode estar sob pressão da memória por causa de operações grandes de consulta, mas, pensando bem, é mais provável que o seu sistema esteja configurado com muito menos memória do que o necessário (nunca há memória suficiente para satisfazer todas as consultas que você está tentando compilar ou há outros processos na caixa que estão roubando memória do SQL Server).

Você pode dar uma olhada no que o SQL Server está configurado usando:

EXEC sp_configure 'max server memory';    -- max configured in MB

SELECT counter_name, cntr_value
  FROM sys.dm_os_performance_counters
  WHERE counter_name IN
  (
    'Total Server Memory (KB)',    -- max currently granted
    'Target Server Memory (KB)'    -- how much SQL Server wished it had
  );

Você pode identificar os planos em cache que exigiram mais memória compilada com a seguinte consulta de Jonathan Kehayias , adaptada levemente:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

;WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT TOP (10) CompileTime_ms, CompileCPU_ms, CompileMemory_KB,
  qs.execution_count,
  qs.total_elapsed_time/1000.0 AS duration_ms,
  qs.total_worker_time/1000.0 as cputime_ms,
  (qs.total_elapsed_time/qs.execution_count)/1000.0 AS avg_duration_ms,
  (qs.total_worker_time/qs.execution_count)/1000.0 AS avg_cputime_ms,
  qs.max_elapsed_time/1000.0 AS max_duration_ms,
  qs.max_worker_time/1000.0 AS max_cputime_ms,
  SUBSTRING(st.text, (qs.statement_start_offset / 2) + 1,
    (CASE qs.statement_end_offset
      WHEN -1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset
     END - qs.statement_start_offset) / 2 + 1) AS StmtText,
  query_hash, query_plan_hash
FROM
(
  SELECT 
    c.value('xs:hexBinary(substring((@QueryHash)[1],3))', 'varbinary(max)') AS QueryHash,
    c.value('xs:hexBinary(substring((@QueryPlanHash)[1],3))', 'varbinary(max)') AS QueryPlanHash,
    c.value('(QueryPlan/@CompileTime)[1]', 'int') AS CompileTime_ms,
    c.value('(QueryPlan/@CompileCPU)[1]', 'int') AS CompileCPU_ms,
    c.value('(QueryPlan/@CompileMemory)[1]', 'int') AS CompileMemory_KB,
    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 qp.query_plan.nodes('ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple') AS n(c)
) AS tab
JOIN sys.dm_exec_query_stats AS qs ON tab.QueryHash = qs.query_hash
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
ORDER BY CompileMemory_KB DESC
OPTION (RECOMPILE, MAXDOP 1);

Você pode ver como o cache do plano está sendo usado com o seguinte:

SELECT objtype, cacheobjtype,
    AVG(size_in_bytes*1.0)/1024.0/1024.0,
    MAX(size_in_bytes)/1024.0/1024.0,
    SUM(size_in_bytes)/1024.0/1024.0,
    COUNT(*)
FROM sys.dm_exec_cached_plans
GROUP BY GROUPING SETS ((),(objtype, cacheobjtype))
ORDER BY objtype, cacheobjtype;

Quando estiver com altas esperas de semáforo, verifique se esses resultados da consulta variam significativamente de durante a atividade "normal":

SELECT resource_semaphore_id, -- 0 = regular, 1 = "small query"
  pool_id,
  available_memory_kb,
  total_memory_kb,
  target_memory_kb
FROM sys.dm_exec_query_resource_semaphores;

SELECT StmtText = SUBSTRING(st.[text], (qs.statement_start_offset / 2) + 1,
        (CASE qs.statement_end_offset
          WHEN -1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset
         END - qs.statement_start_offset) / 2 + 1),
  r.start_time, r.[status], DB_NAME(r.database_id), r.wait_type, 
  r.last_wait_type, r.total_elapsed_time, r.granted_query_memory,
  m.requested_memory_kb, m.granted_memory_kb, m.required_memory_kb,
  m.used_memory_kb
FROM sys.dm_exec_requests AS r
INNER JOIN sys.dm_exec_query_stats AS qs
ON r.plan_handle = qs.plan_handle
INNER JOIN sys.dm_exec_query_memory_grants AS m
ON r.request_id = m.request_id
AND r.plan_handle = m.plan_handle
CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS st;

E você também pode querer ver e ver como a memória é distribuída:

DBCC MEMORYSTATUS;

E há algumas boas informações aqui sobre por que você pode estar vendo um grande número de compilações / recompilações (o que contribuirá para essa espera):

http://technet.microsoft.com/en-us/library/ee343986(v=sql.100).aspx

http://technet.microsoft.com/en-us/library/cc293620.aspx

Você pode verificar altas contagens de compilação / recompilação usando os seguintes contadores:

SELECT counter_name, cntr_value
  FROM sys.dm_os_performance_counters
  WHERE counter_name IN 
  (
    'SQL Compilations/sec',
    'SQL Re-Compilations/sec'
  );

E você pode verificar a pressão da memória interna levando a despejos - contadores diferentes de zero aqui indicariam que algo de ruim está acontecendo com o cache do plano:

SELECT * FROM sys.dm_os_memory_cache_clock_hands 
  WHERE [type] IN (N'CACHESTORE_SQLCP', N'CACHESTORE_OBJCP');

OBSERVAÇÃO A maioria dessas métricas não possui uma mágica "oh meu Deus, eu preciso entrar em pânico ou fazer alguma coisa!" limite. O que você precisa fazer é fazer medições durante a atividade normal do sistema e determinar onde esses limites são para seu hardware, configuração e carga de trabalho. Quando você entra em pânico , algo ocorre quando duas condições são verdadeiras:

  1. as métricas variam significativamente dos valores normais; e,
  2. na verdade, está ocorrendo um problema de desempenho (como picos de CPU) - mas apenas se eles estiverem realmente interferindo em alguma coisa. Além de ver os picos de CPU, você está vendo algum outro sintoma? Em outras palavras, o aumento é o sintoma ou está causando outros sintomas? Os usuários do sistema notariam alguma vez? Muitas pessoas sempre buscam o consumidor com maior espera, simplesmente porque é o mais alto. Algo sempre será o consumidor com maior espera - você precisa saber que está variando o suficiente da atividade normal para indicar um problema ou alguma mudança significativa.

Optimize for ad hoc workloadsé uma excelente configuração para 99% das cargas de trabalho disponíveis, mas não será muito útil para reduzir os custos de compilação - ele visa reduzir o inchaço do cache do plano, impedindo que um plano de uso único armazene todo o plano até que seja executado duas vezes . Mesmo quando você armazena apenas o stub no cache do plano, você ainda precisa compilar o plano completo para a execução da consulta. Talvez o que a @Kahn quisesse recomendar fosse definir a parametrização no nível do banco de dados como forçada , o que potencialmente fornecerá uma melhor reutilização do plano (mas isso realmente depende de quão únicas são todas essas consultas de alto custo).

Também, algumas boas informações neste white paper sobre o cache e a compilação do plano.


Atualmente, temos o Optimize for ad hoc workloadsconjunto, embora, como você mencionou, não seja realmente relevante para esse problema em particular. Temos um código que gera muitas consultas exclusivas, algumas de uma ferramenta ORM, outras codificadas manualmente. Até onde eu sei, os picos de CPU não estão ocorrendo por tempo suficiente para que nossos usuários percebam. Definir o banco de dados para parametrização forçada parece perigoso para mim.
DanM

Uma pergunta - quando você verifica compilações altas, o que realmente constitui um número alto? Eu pensei que compilações / s só eram significativas quando comparadas ao número de solicitações em lote / s.
DanM

@ DanM como eu disse acima, não há como eu saber o que pode ser alto para o seu ambiente, porque não tenho idéia do que é normal para o seu ambiente. Se o número for próximo ou superior ao número de solicitações em lote / s, isso pode ser um indicador, mas novamente depende. Por exemplo, se seus lotes consistirem em 5000 instruções e 10 deles exigirem recompilação (como isso pode acontecer no nível da instrução), o comp / s será 10x em comparação ao lote / s. Isso é um problema?
Aaron Bertrand

@DanM Além disso, você deve tomar qualquer recomendação para alterar um banco de dados ou configuração global com um aviso implícito de que é algo que você deve testar e não apenas ativar, porque alguém na Internet disse que o fez. :-) Tento explicar como e por que uma mudança pode ajudar, mas nem sempre me lembro de afirmar o óbvio: teste primeiro .
Aaron Bertrand

Entendo seu ponto de vista - o que constitui compilações "altas" depende totalmente do ambiente.
DanM

-1

De longe, a razão mais comum pela qual vi essas esperas aparecerem é o resultado de índices fragmentados ou insuficientes e estatísticas que têm tamanho de amostra insuficiente ou são obsoletas. Isso resulta em varreduras maciças de tabelas completas que consomem toda a memória, o que, por sua vez, produz um sintoma que frequentemente vemos como RESOURCE_SEMAPHORE_QUERY_COMPILE.

A maneira mais fácil de verificar isso é verificar se as consultas executam varreduras de tabela completa / varreduras de índice, quando deveriam fazer buscas de índice. Se você tiver uma consulta com o qual possa reproduzir o problema, torna-se muito fácil diagnosticar e corrigir isso.

Gostaria de verificar os índices nas tabelas afetadas por essas consultas de problemas - ou seja. verifique a fragmentação do índice, os possíveis índices filtrados que não são usados, os índices ausentes que você deseja criar, etc. Além disso, atualize suas estatísticas com o FULLSCAN o mais rápido possível.

Um bom ponto a ser lembrado é que sua tabela de problemas pode não ser a única que precisa disso. Por exemplo, se você tiver uma consulta que busca dados de 10 tabelas, o planejador de execução pode ocasionalmente mostrar que não está usando o índice na tabela 1, mas quando você verifica o índice na tabela 1, está tudo bem. O planejador de consultas pode resolver buscar os dados na tabela 1 com uma varredura completa da tabela corretamente, porque um índice com defeito / insuficiente na tabela 7, por exemplo, retornou tantos dados que essa seria a opção mais rápida. Portanto, diagnosticar isso às vezes pode ser complicado.

Além disso, se você tiver muitas consultas por trás do código, com apenas algumas alterações nos valores das variáveis, por exemplo, convém ativar a otimização para cargas de trabalho ad hoc . Basicamente, o que ele faz é armazenar um esboço do plano compilado em vez de todo o plano, economizando recursos quando você nunca obtém exatamente os mesmos planos todas as vezes.


A maioria das coisas destacadas causaria planos de consulta ineficientes, não altos tempos de compilação. NA MINHA HUMILDE OPINIÃO.
Aaron Bertrand

No entanto, já vi isso acontecer várias vezes e essa espera foi exatamente o que se seguiu. Obviamente, não tenho idéia de quão comum é ou se aplica aqui, mas os métodos mencionados acima o corrigiram.
Kahn

Como uma edição posterior, no nosso caso, ele aparece em um banco de dados bastante grande depois que os índices / estatísticas críticos são afetados, usados ​​por um grande número de consultas sendo executadas o tempo todo.
Kahn

11
Certo, e as esperas de compilação ocorreram porque os índices / estatísticas foram alterados, o que fez com que todos os planos relevantes fossem recompilados, não porque estavam fragmentados ou desatualizados (como os estados da resposta).
Aaron Bertrand
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.