Intermittierende RESOURCE_SEMAPHORE_QUERY_COMPILE Wartestatistiken

8

Ich versuche, einige zeitweise auftretende CPU-Spitzen zu beheben, die auf einem unserer Produktions-SQL-Server auftreten. Wir führen SQL Server 2008 R2 Standard Edition mit 28 GB RAM und 4 CPU-Kernen aus. In diesem Fall stellen wir fest, dass eine große Anzahl von RESOURCE_SEMAPHORE_QUERY_COMPILER-Wartezeiten etwa ein bis zwei Minuten dauert und dann stoppt, wodurch sich die CPU-Auslastung wieder normalisiert.

Nachdem ich dies untersucht habe, verstehe ich, dass dies normalerweise durch das Kompilieren vieler nicht wiederverwendbarer Ausführungspläne verursacht wird, an denen wir derzeit an Änderungen an unserer Anwendung arbeiten, um sie zu beheben.

Kann dieses Verhalten auch durch Plan-Cache-Räumungen aufgrund von Speicherdruck ausgelöst werden? Wenn ja, wie würde ich das überprüfen? Ich versuche herauszufinden, ob wir kurzfristige Abhilfemaßnahmen ergreifen können, z. B. das Aktualisieren des Server-RAM, bis wir unsere Anwendungskorrekturen bereitstellen. Die einzige andere kurzfristige Option, die ich mir vorstellen kann, besteht darin, einige unserer am stärksten ausgelasteten Datenbanken auf einen anderen Server zu verschieben.

DanM
quelle

Antworten:

6

Ich glaube, Sie werden dieses Symptom sehen, wenn Sie viele große Abfragepläne haben, die um Speicher kämpfen, um kompiliert zu werden (dies hat sehr wenig mit dem Ausführen der Abfrage selbst zu tun). Ich vermute, Sie verwenden ein ORM oder eine Anwendung, die viele eindeutige, aber relativ komplexe Abfragen generiert. SQL Server könnte aufgrund umfangreicher Abfragevorgänge unter Speicherdruck stehen. Bei näherer Betrachtung ist es jedoch wahrscheinlicher, dass Ihr System mit weitaus weniger Speicher konfiguriert ist, als es benötigt (entweder ist nie genügend Speicher vorhanden, um alle von Ihnen gestellten Abfragen zu erfüllen versuchen zu kompilieren, oder es gibt andere Prozesse auf der Box, die Speicher von SQL Server stehlen.

Sie können sich ansehen, wie SQL Server konfiguriert ist, indem Sie Folgendes verwenden:

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

Mit der folgenden Jonathan Kehayias-Abfrage , die leicht angepasst wurde, können Sie die zwischengespeicherten Pläne identifizieren, für die der meiste Kompilierungsspeicher erforderlich ist :

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

Sie können sehen, wie der Plan-Cache wie folgt verwendet wird:

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;

Überprüfen Sie bei hohen Semaphor-Wartezeiten, ob diese Abfrageergebnisse erheblich von den "normalen" Aktivitäten abweichen:

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;

Vielleicht möchten Sie auch sehen, wie der Speicher verteilt ist:

DBCC MEMORYSTATUS;

Und hier gibt es einige gute Informationen darüber, warum möglicherweise eine große Anzahl von Kompilierungen / Neukompilierungen angezeigt wird (was zu diesem Warten beiträgt):

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

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

Mit den folgenden Zählern können Sie nach hohen Kompilierungs- / Neukompilierungszahlen suchen:

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

Und Sie können nach internem Speicherdruck suchen, der zu Räumungen führt. Zähler ungleich Null deuten darauf hin, dass mit dem Plan-Cache etwas nicht Gutes passiert:

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

HINWEIS Die meisten dieser Metriken haben keine magische "Oh mein Gott, ich muss in Panik geraten oder etwas tun!" Schwelle. Sie müssen während der normalen Systemaktivität Messungen durchführen und feststellen, wo diese Schwellenwerte für Ihre Hardware, Konfiguration und Arbeitslast liegen. Wenn Sie in Panik geraten, tun Sie etwas, wenn zwei Bedingungen erfüllt sind:

  1. Die Metriken weichen erheblich von den normalen Werten ab. und,
  2. Es tritt tatsächlich ein Leistungsproblem auf (wie bei Ihren CPU-Spitzen) - aber nur, wenn sie tatsächlich irgendetwas stören. Sehen Sie außer der Spitze der CPUs ein anderes Symptom? Mit anderen Worten, ist die Spitze das Symptom oder verursacht die Spitze andere Symptome? Würden Benutzer des Systems jemals bemerken? Viele Leute streben immer nach ihrem höchsten Warteverbraucher, einfach weil es der höchste ist. Etwas wird immer der Verbraucher mit der höchsten Wartezeit sein - Sie müssen wissen, dass es so weit von der normalen Aktivität abweicht, dass es auf ein Problem oder eine signifikante Änderung hinweist.

Optimize for ad hoc workloadsist eine großartige Einstellung für 99% der Workloads, aber sie ist nicht sehr hilfreich bei der Reduzierung der Kompilierungskosten. Sie zielt darauf ab, das Aufblähen des Plan-Cache zu reduzieren, indem verhindert wird, dass ein Einwegplan den gesamten Plan speichert, bis er zweimal ausgeführt wird . Auch wenn Sie den Stub nur im Plan-Cache speichern, müssen Sie den vollständigen Plan für die Ausführung der Abfrage kompilieren. Vielleicht wollte @Kahn empfehlen, die Parametrisierung auf Datenbankebene auf erzwungen zu setzen , was möglicherweise eine bessere Wiederverwendung des Plans ermöglicht (aber es hängt wirklich davon ab, wie einzigartig all diese kostenintensiven Abfragen sind).

In diesem Whitepaper finden Sie auch einige gute Informationen zum Plan-Caching und zur Kompilierung.

Aaron Bertrand
quelle
Wir haben derzeit das Optimize for ad hoc workloadsSet, aber wie Sie bereits erwähnt haben, ist es für dieses spezielle Problem nicht wirklich relevant. Wir haben Code, der viele eindeutige Abfragen generiert, einige von einem ORM-Tool, andere von Hand codiert. Soweit ich weiß, treten die CPU-Spitzen nicht lange genug auf, damit unsere Benutzer sie bemerken. Das Einstellen der Datenbank auf erzwungene Parametrisierung klingt für mich gefährlich.
DanM
Eine Frage: Wenn Sie nach hohen Zusammenstellungen suchen, was macht eigentlich eine hohe Zahl aus? Ich dachte, Kompilierungen / Sek. Waren nur im Vergleich zur Anzahl der Stapelanforderungen / Sek. Sinnvoll.
DanM
@DanM Wie ich oben sagte, gibt es für mich keine Möglichkeit zu wissen, was für Ihre Umgebung hoch sein könnte, da ich keine Ahnung habe, was für Ihre Umgebung normal ist. Wenn die Anzahl nahe oder höher als die Anzahl der Stapelanforderungen / Sek. Ist, kann dies ein Indikator sein, hängt aber wiederum davon ab. Wenn Ihre Stapel beispielsweise aus 5000 Anweisungen bestehen und 10 davon neu kompiliert werden müssen (da dies auf Anweisungsebene geschehen kann), beträgt comp / sec im Vergleich zu batch / sec das 10-fache. Ist das ein Problem?
Aaron Bertrand
@DanM Außerdem sollten Sie jede Empfehlung zum Ändern einer Datenbank oder einer globalen Einstellung mit dem impliziten Haftungsausschluss annehmen, dass dies etwas ist, das Sie testen und nicht nur aktivieren sollten, weil dies jemand im Internet gesagt hat. :-) Ich versuche zu erklären, wie und warum eine Änderung helfen kann, aber ich erinnere mich nicht immer daran, das Offensichtliche zu sagen: Testen Sie es zuerst .
Aaron Bertrand
Ich verstehe Ihren Standpunkt - was "hohe" Zusammenstellungen ausmacht, ist völlig umweltabhängig.
DanM
-1

Der mit Abstand typischste Grund, warum ich diese Wartezeiten gesehen habe, sind fragmentierte oder unzureichende Indizes und Statistiken, die entweder eine unzureichende Stichprobengröße aufweisen oder veraltet sind. Dies führt zu massiven vollständigen Tabellenscans, die den gesamten Speicher belasten, was wiederum ein Symptom erzeugt, das wir häufig als RESOURCE_SEMAPHORE_QUERY_COMPILE sehen.

Der einfachste Weg, dies zu überprüfen, besteht darin, zu überprüfen, ob die Abfragen vollständige Tabellenscans / Indexscans ausführen, wenn sie Indexsuchen durchführen sollten. Wenn Sie eine Problemabfrage haben, mit der Sie das Problem reproduzieren können, ist es sehr einfach, dies zu diagnostizieren und zu beheben.

Ich würde die Indizes für die Tabellen überprüfen, die von diesen Problemabfragen betroffen sind - dh. Überprüfen Sie die Indexfragmentierung, mögliche gefilterte Indizes, die nicht verwendet werden, fehlende Indizes, die Sie möglicherweise erstellen möchten usw. Aktualisieren Sie außerdem ihre Statistiken so bald wie möglich mit FULLSCAN.

Ein guter Punkt, an den Sie sich erinnern sollten, ist, dass Ihre Problemtabelle möglicherweise nicht die einzige ist, die dies benötigt. Wenn Sie beispielsweise eine Abfrage haben, die Daten aus 10 Tabellen abruft, zeigt der Ausführungsplaner gelegentlich an, dass der Index für Tabelle 1 nicht verwendet wird. Wenn Sie dann jedoch den Index für Tabelle 1 überprüfen, ist dies tatsächlich in Ordnung. Der Abfrageplaner kann beschließen, Daten in Tabelle 1 mit einem vollständigen Tabellenscan korrekt abzurufen, da beispielsweise ein fehlerhafter / unzureichender Index in Tabelle 7 so viele Daten zurückgegeben hat, dass dies die schnellere Option wäre. Daher kann die Diagnose manchmal schwierig sein.

Wenn Sie beispielsweise viele Codebehind-Abfragen mit nur wenigen Änderungen der Variablenwerte haben, sollten Sie die Optimierung für Ad-hoc-Workloads aktivieren . Grundsätzlich wird ein Stub des kompilierten Plans anstelle des gesamten Plans gespeichert, wodurch Ressourcen gespart werden, wenn Sie nie jedes Mal genau dieselben Pläne erhalten.

Kahn
quelle
Die meisten der von Ihnen hervorgehobenen Dinge würden zu ineffizienten Abfrageplänen und nicht zu hohen Kompilierungszeiten führen. MEINER BESCHEIDENEN MEINUNG NACH.
Aaron Bertrand
Trotzdem habe ich das schon mehrmals gesehen und dieses Warten war oft genau das, was folgte. Natürlich habe ich keine Ahnung, wie häufig es ist oder ob es hier gilt, aber die oben genannten Methoden haben es behoben.
Kahn
Als Nachbearbeitung wird es in unserem Fall in einer ziemlich großen Datenbank angezeigt, nachdem kritische Indizes / Statistiken betroffen waren, die von einer großen Anzahl von Abfragen verwendet wurden, die ständig ausgeführt wurden.
Kahn
1
Richtig, und die Wartezeiten beim Kompilieren kamen, weil Indizes / Statistiken geändert wurden, wodurch alle relevanten Pläne neu kompiliert wurden, nicht weil sie fragmentiert oder veraltet waren (wie in Ihren Antwortzuständen angegeben).
Aaron Bertrand