Statistiques d'attente RESOURCE_SEMAPHORE_QUERY_COMPILE intermittentes

8

J'essaie de résoudre certains pics de CPU intermittents que nous observons sur l'un de nos serveurs SQL de production. Nous exécutons SQL Server 2008 R2 Standard Edition avec 28 Go de RAM et 4 cœurs de processeur. Lorsque cela se produit, nous remarquons un grand nombre d'attentes RESOURCE_SEMAPHORE_QUERY_COMPILER, qui durent environ une minute ou deux, puis s'arrêtent, ce qui fait que l'utilisation du processeur revient à la normale.

Après avoir étudié cela, je comprends que cela est normalement dû à la compilation de nombreux plans d'exécution non réutilisables, sur lesquels nous travaillons actuellement sur des modifications à apporter à notre application.

Ce comportement peut-il également être déclenché par des expulsions de cache planifiées en raison de la pression sur la mémoire? Si oui, comment pourrais-je vérifier cela? J'essaie de voir s'il existe des remèdes à court terme, comme la mise à niveau de la RAM du serveur, jusqu'à ce que nous déployions nos correctifs d'application. La seule autre option à court terme à laquelle je peux penser est de déplacer certaines de nos bases de données les plus actives vers un serveur différent.

DanM
la source

Réponses:

6

Je crois que vous verrez ce symptôme si vous avez BEAUCOUP de plans de requête volumineux qui se battent pour la mémoire afin de compiler (cela a très peu à voir avec l'exécution de la requête elle-même). Pour cela, je soupçonne que vous utilisez un ORM ou une sorte d'application qui génère de nombreuses requêtes uniques mais relativement complexes. SQL Server peut être sous pression en raison de choses telles que les opérations de requête volumineuses, mais après réflexion, il est plus probable que votre système soit configuré avec beaucoup moins de mémoire qu'il n'en a besoin (soit il n'y a jamais assez de mémoire pour satisfaire toutes les requêtes que vous essayez de compiler, ou il y a d'autres processus sur la boîte qui volent la mémoire de SQL Server).

Vous pouvez jeter un œil à la configuration de SQL Server à l'aide de:

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
  );

Vous pouvez identifier les plans mis en cache qui nécessitaient le plus de mémoire de compilation avec la requête Jonathan Kehayias suivante , légèrement adaptée:

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);

Vous pouvez voir comment le cache de plan est utilisé avec les éléments suivants:

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;

Lorsque vous rencontrez des attentes élevées de sémaphore, vérifiez si ces résultats de requête varient considérablement par rapport à une activité "normale":

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;

Et vous voudrez peut-être aussi regarder et voir comment la mémoire est distribuée:

DBCC MEMORYSTATUS;

Et il y a de bonnes informations ici sur les raisons pour lesquelles vous pourriez voir un nombre élevé de compilations / recompilations (ce qui contribuera à cette attente):

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

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

Vous pouvez vérifier les nombres élevés de compilation / recompilation à l'aide des compteurs suivants:

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

Et vous pouvez vérifier la pression de la mémoire interne conduisant à des expulsions - des compteurs non nuls ici indiqueraient qu'il se passe quelque chose de mal avec le cache du plan:

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

REMARQUE La plupart de ces mesures n'ont pas de magie "oh mon Dieu, j'ai besoin de paniquer ou de faire quelque chose!" seuil. Ce que vous devez faire est de prendre des mesures pendant l' activité normale du système et de déterminer où se trouvent ces seuils pour votre matériel, votre configuration et votre charge de travail. Lorsque vous paniquez , quelque chose se produit lorsque deux conditions sont remplies:

  1. les mesures varient considérablement des valeurs normales; et,
  2. il y a en fait un problème de performances (comme vos pics de processeur) - mais seulement s'ils interfèrent réellement avec quoi que ce soit. En plus de voir le pic des processeurs, voyez-vous un autre symptôme? En d'autres termes, la pointe est-elle le symptôme ou la pointe provoque-t-elle d'autres symptômes? Les utilisateurs du système le remarqueraient-ils jamais? Beaucoup de gens recherchent toujours leur consommateur qui attend le plus, simplement parce que c'est le plus élevé. Quelque chose sera toujours le consommateur qui attend le plus - vous devez savoir qu'il diffère suffisamment de l'activité normale pour indiquer un problème ou un changement significatif.

Optimize for ad hoc workloadsest un cadre idéal pour 99% des charges de travail, mais il ne sera pas très utile pour réduire les coûts de compilation - il vise à réduire le gonflement du cache du plan en empêchant un plan à usage unique de stocker le plan entier jusqu'à ce qu'il soit exécuté deux fois . Même lorsque vous ne stockez que le stub dans le cache du plan, vous devez toujours compiler le plan complet pour l'exécution de la requête. Peut-être que @Kahn voulait recommander de définir le paramétrage du niveau de la base de données sur forcé , ce qui pourrait potentiellement permettre une meilleure réutilisation du plan (mais cela dépend vraiment de la spécificité de toutes ces requêtes coûteuses).

Également quelques bonnes informations dans ce livre blanc sur la mise en cache et la compilation des plans.

Aaron Bertrand
la source
Nous avons actuellement l' Optimize for ad hoc workloadsensemble, mais, comme vous l'avez mentionné, il n'est pas vraiment pertinent pour cette question particulière. Nous avons du code qui génère de nombreuses requêtes uniques, certaines à partir d'un outil ORM, d'autres codées à la main. Pour autant que je sache, les pics de CPU ne se produisent pas assez longtemps pour que nos utilisateurs le remarquent. Définir la base de données sur un paramétrage forcé me semble dangereux.
DanM
Une question - lorsque vous recherchez des compilations élevées, qu'est-ce qui constitue réellement un nombre élevé? Je pensais que les compilations / s n'avaient de sens que par rapport au nombre de requêtes batch / s.
DanM
@DanM comme je l'ai dit ci-dessus, il n'y a aucun moyen pour moi de savoir ce qui pourrait être élevé pour votre environnement, car je n'ai aucune idée de ce qui est normal pour votre environnement. Si le nombre est proche ou supérieur au nombre de demandes de lot / s, cela peut être un indicateur, mais encore une fois cela dépend. Par exemple, si vos lots se composent de 5000 instructions et 10 d'entre elles nécessitent des recompilations (car cela peut se produire au niveau de l'instruction), comp / sec va être 10x comparé à batch / sec. Est-ce un problème?
Aaron Bertrand
@DanM Vous devriez également prendre toute recommandation de modifier une base de données ou un paramètre global avec un avertissement implicite que c'est quelque chose que vous devez tester et pas seulement activer parce que quelqu'un sur Internet l'a dit. :-) J'essaie d'expliquer comment et pourquoi un changement peut aider, mais je ne me souviens pas toujours de dire l'évidence: testez-le d'abord .
Aaron Bertrand
Je vois votre point - ce qui constitue des compilations «élevées» est entièrement dépendant de l'environnement.
DanM
-1

De loin, la raison la plus typique pour laquelle j'ai vu ces attentes apparaître est le résultat d'index fragmentés ou insuffisants et de statistiques qui ont soit une taille d'échantillon insuffisante, soit sont obsolètes. Il en résulte des analyses de table complètes massives qui accaparent toute la mémoire, ce qui à son tour produit un symptôme que nous considérons souvent comme RESOURCE_SEMAPHORE_QUERY_COMPILE.

Le moyen le plus simple de vérifier cela est de vérifier si les requêtes exécutent des analyses de table / analyses d'index complètes, alors qu'elles devraient effectuer des recherches d'index. Si vous avez une requête de problème avec laquelle vous pouvez reproduire le problème - il devient très facile de diagnostiquer et de résoudre ce problème.

Je vérifierais les index sur les tables affectées par ces requêtes problématiques - ie. vérifiez la fragmentation des index, les index filtrés potentiels qui ne sont pas utilisés, les index manquants que vous voudrez peut-être créer, etc. De plus, mettez à jour leurs statistiques avec FULLSCAN dès que possible.

Un bon point à retenir est que votre table de problèmes n'est peut-être pas la seule à en avoir besoin. Par exemple, si vous avez une requête qui récupère les données de 10 tables, le planificateur d'exécution peut parfois montrer qu'il n'utilise pas l'index sur la table 1, mais lorsque vous vérifiez ensuite l'index sur la table 1, c'est en fait correct. Le planificateur de requêtes peut résoudre pour récupérer correctement les données sur la table 1 avec un balayage complet de la table, car un index défectueux / insuffisant sur la table 7 par exemple, a renvoyé tellement de données que ce serait l'option la plus rapide. Il est donc parfois difficile de les diagnostiquer.

De plus, si vous avez beaucoup de requêtes codebehind avec seulement quelques changements dans les valeurs des variables par exemple, vous voudrez peut-être envisager d' activer l'optimisation pour les charges de travail ad hoc . Fondamentalement, il stocke un talon du plan compilé au lieu du plan entier, économisant des ressources lorsque vous n'obtenez jamais exactement les mêmes plans à chaque fois.

Kahn
la source
La plupart des choses que vous mettez en évidence entraîneraient des plans de requête inefficaces, et non des temps de compilation élevés. A MON HUMBLE AVIS.
Aaron Bertrand
Néanmoins, j'ai vu cela se produire plusieurs fois et cette attente a souvent été exactement ce qui a suivi. Évidemment, je n'ai aucune idée de sa fréquence ou de son application ici, mais les méthodes mentionnées ci-dessus l'ont corrigé.
Kahn
En tant que post-édition, dans notre cas, il est apparu dans une base de données assez grande après que les index / statistiques critiques ont été affectés et utilisés par un grand nombre de requêtes exécutées tout le temps.
Kahn
1
D'accord, et les attentes de compilation sont venues parce que les index / statistiques ont été modifiés, ce qui a entraîné la recompilation de tous les plans pertinents, et non parce qu'ils étaient fragmentés ou obsolètes (comme l'indique votre réponse).
Aaron Bertrand