Credo che vedrai questo sintomo se hai MOLTI piani di query di grandi dimensioni che stanno lottando per la memoria per la compilazione (questo ha ben poco a che fare con l'esecuzione della query stessa). Per colpirlo, sospetto che tu stia utilizzando un ORM o qualche tipo di applicazione che genera molte query uniche ma relativamente complesse. SQL Server potrebbe essere sotto pressione della memoria a causa di operazioni come le query di grandi dimensioni, ma in più è più probabile che il tuo sistema sia configurato con molta meno memoria del necessario (o non c'è mai abbastanza memoria per soddisfare tutte le query che hai stai provando a compilare, o ci sono altri processi sulla scatola che stanno rubando memoria da SQL Server).
Puoi dare un'occhiata a cosa è configurato SQL Server 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
);
È possibile identificare i piani memorizzati nella cache che richiedono la memoria più compilata con la seguente query Jonathan Kehayias , adattata leggermente:
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);
Puoi vedere come viene utilizzata la cache del piano con quanto segue:
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 si verificano attese con semaforo elevato, verificare se i risultati di queste query variano in modo significativo durante l'attività "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;
E potresti anche voler guardare e vedere come viene distribuita la memoria:
DBCC MEMORYSTATUS;
E ci sono alcune buone informazioni qui sul motivo per cui potresti vedere un numero elevato di compilazioni / ricompilazioni (che contribuiranno a quell'attesa):
http://technet.microsoft.com/en-us/library/ee343986(v=sql.100).aspx
http://technet.microsoft.com/en-us/library/cc293620.aspx
È possibile verificare i conteggi di compilazione / ricompilazione elevati utilizzando i seguenti contatori:
SELECT counter_name, cntr_value
FROM sys.dm_os_performance_counters
WHERE counter_name IN
(
'SQL Compilations/sec',
'SQL Re-Compilations/sec'
);
E puoi verificare la pressione della memoria interna che porta agli sfratti: i contatori diversi da zero qui indicano che qualcosa non va bene con la cache del piano:
SELECT * FROM sys.dm_os_memory_cache_clock_hands
WHERE [type] IN (N'CACHESTORE_SQLCP', N'CACHESTORE_OBJCP');
NOTA La maggior parte di queste metriche non ha una magia "oh mio Dio, devo andare nel panico o fare qualcosa!" soglia. Quello che devi fare è prendere le misure durante la normale attività del sistema e determinare dove si trovano queste soglie per l'hardware, la configurazione e il carico di lavoro. Quando ti fai prendere dal panico qualcosa succede quando due condizioni sono vere:
- le metriche variano significativamente dai valori normali; e,
- si verifica effettivamente un problema di prestazioni (come i picchi della CPU), ma solo se interferiscono effettivamente con qualcosa. Oltre a vedere il picco delle CPU, vedi qualche altro sintomo? In altre parole, il picco è il sintomo o il picco sta causando altri sintomi? Gli utenti del sistema lo noterebbero mai? Molte persone cercano sempre il consumatore più in attesa, semplicemente perché è il più alto. Qualcosa sarà sempre il consumatore più in attesa: devi sapere che varia abbastanza dalla normale attività da indicare un problema o qualche cambiamento significativo.
Optimize for ad hoc workloads
è un'ottima impostazione per il 99% dei carichi di lavoro disponibili, ma non sarà molto utile per ridurre i costi di compilazione: mira a ridurre il gonfiore della cache del piano impedendo a un piano monouso di archiviare l'intero piano fino a quando non viene eseguito due volte . Anche quando si memorizza solo lo stub nella cache del piano, è comunque necessario compilare il piano completo per l'esecuzione della query. Forse ciò che @Kahn intendeva raccomandare era impostare la parametrizzazione a livello di database su forzata , che potenzialmente fornirà un migliore riutilizzo del piano (ma dipende davvero da quanto siano uniche tutte queste query ad alto costo).
Anche alcune buone informazioni in questo white paper sulla cache e la compilazione del piano.
Optimize for ad hoc workloads
set, anche se, come hai detto, non è davvero rilevante per questo particolare problema. Abbiamo un codice che genera molte query uniche, alcune da uno strumento ORM, altre codificate a mano. Per quanto ne so, i picchi della CPU non si verificano abbastanza a lungo da essere notati dai nostri utenti. L'impostazione del database sulla parametrizzazione forzata mi sembra pericolosa.