Я полагаю, вы увидите этот симптом, если у вас будет МНОГО больших планов запросов, которые борются за память для компиляции (это очень мало связано с выполнением самого запроса). Я подозреваю, что для этого вы используете ORM или какое-то приложение, которое генерирует множество уникальных, но относительно сложных запросов. SQL Server может испытывать нехватку памяти из-за таких вещей, как операции с большими запросами, но, если подумать, более вероятно, что ваша система сконфигурирована с гораздо меньшим объемом памяти, чем нужно (либо памяти никогда не будет достаточно для удовлетворения всех запросов, которые вы пытаетесь скомпилировать, или на коробке есть другие процессы, которые крадут память у SQL Server).
Вы можете взглянуть на то, что SQL Server настроен с помощью:
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
);
Вы можете определить кэшированные планы, для которых требовалось наибольшее количество компилируемой памяти, с помощью следующего слегка адаптированного запроса Джонатана Кейяса :
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);
Вы можете увидеть, как кэш плана используется со следующим:
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;
Когда вы испытываете большое время ожидания семафора, проверьте, значительно ли отличаются результаты этих запросов от "нормальной" активности:
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;
И вы также можете посмотреть, как распределяется память:
DBCC MEMORYSTATUS;
И здесь есть некоторая хорошая информация о том, почему вы можете увидеть большое количество компиляций / перекомпиляций (что будет способствовать этому ожиданию):
http://technet.microsoft.com/en-us/library/ee343986(v=sql.100).aspx
http://technet.microsoft.com/en-us/library/cc293620.aspx
Вы можете проверить высокие показатели компиляции / перекомпиляции, используя следующие счетчики:
SELECT counter_name, cntr_value
FROM sys.dm_os_performance_counters
WHERE counter_name IN
(
'SQL Compilations/sec',
'SQL Re-Compilations/sec'
);
И вы можете проверить наличие внутренней памяти, приводящее к выселению - ненулевые счетчики здесь будут указывать, что с кэшем плана происходит что-то нехорошее:
SELECT * FROM sys.dm_os_memory_cache_clock_hands
WHERE [type] IN (N'CACHESTORE_SQLCP', N'CACHESTORE_OBJCP');
ПРИМЕЧАНИЕ. Большинство из этих показателей не имеют волшебства: «О, черт возьми, мне нужно паниковать или что-то делать!» порог. Вам нужно провести измерения во время нормальной работы системы и определить, где находятся эти пороговые значения для вашего оборудования, конфигурации и рабочей нагрузки. Когда вы паникуете, вы делаете что-то, когда выполняются два условия:
- показатели значительно отличаются от нормальных значений; а также,
- на самом деле возникает проблема с производительностью (например, пики вашего процессора) - но только если они на самом деле что-то мешают. Кроме того, что вы видите всплеск процессоров, вы видите какой-нибудь другой симптом? Другими словами, является ли спайк симптомом или спайк вызывает другие симптомы? Заметят ли пользователи системы когда-нибудь? Многие люди всегда идут за своим потребителем с самым высоким ожиданием, просто потому, что он самый высокий. Что-то всегда будет потребителем с наивысшим ожиданием - вы должны знать, что оно достаточно отличается от обычной активности, что указывает на проблему или какое-то существенное изменение.
Optimize for ad hoc workloads
Это отличная настройка для 99% рабочих нагрузок, но она не будет очень полезна для снижения затрат на компиляцию - она направлена на уменьшение размаха кэша плана за счет предотвращения хранения плана всего одноразового использования до тех пор, пока он не будет выполнен дважды. , Даже когда вы сохраняете только заглушку в кэше планов, вам все равно приходится составлять полный план для выполнения запроса. Возможно, то, что @Kahn хотел порекомендовать, это установить принудительную параметризацию уровня базы данных , что потенциально обеспечит лучшее повторное использование плана (но это действительно зависит от того, насколько уникальны все эти дорогостоящие запросы).
Также есть хорошая информация в этой статье о кэшировании и компиляции планов.
Optimize for ad hoc workloads
набор, хотя, как вы упомянули, он не имеет отношения к этой конкретной проблеме. У нас есть код, который генерирует множество уникальных запросов, некоторые из которых сделаны из инструмента ORM, некоторые из них написаны вручную. Насколько я знаю, пики процессора не происходят достаточно долго, чтобы наши пользователи могли это заметить. Настройка базы данных на принудительную параметризацию звучит для меня опасно.До сих пор я видел наиболее типичную причину появления этих ожиданий в результате фрагментированных или недостаточных индексов и статистики, которая либо имеет недостаточный размер выборки, либо устарела. Это приводит к массивному сканированию полной таблицы, в результате чего происходит переполнение всей памяти, что, в свою очередь, приводит к появлению симптома, который мы часто видим как RESOURCE_SEMAPHORE_QUERY_COMPILE.
Самый простой способ проверить это - проверить, выполняют ли запросы полное сканирование таблицы / сканирование индекса, когда они должны выполнять поиск индекса. Если у вас есть проблемный запрос, с помощью которого вы можете воспроизвести проблему - становится очень легко диагностировать и исправить это.
Я бы проверил индексы на таблицах, затронутых этими проблемными запросами - т.е. проверьте фрагментацию индекса, потенциальные отфильтрованные индексы, которые не используются, отсутствующие индексы, которые вы, возможно, захотите создать, и т. д. Также обновите их статистику с помощью FULLSCAN как можно скорее.
Следует помнить, что ваша таблица проблем может быть не единственной, которая нуждается в этом. Например, если у вас есть запрос, который выбирает данные из 10 таблиц, планировщик выполнения может иногда показывать, что он не использует индекс в таблице 1, но когда вы затем проверяете индекс в таблице 1, это на самом деле нормально. Планировщик запросов может разрешить выборку данных из таблицы 1 с полным сканированием таблицы, поскольку, например, из-за неверного / недостаточного индекса в таблице 7 было возвращено так много данных, что это был бы более быстрый вариант. Так что диагностировать их иногда бывает сложно.
Кроме того, если у вас есть много запросов с выделенным кодом, например, с несколькими изменениями значений переменных, вы можете рассмотреть возможность оптимизации для специальных рабочих нагрузок . По сути, он сохраняет заглушку скомпилированного плана вместо всего плана, экономя ресурсы, когда вы никогда не получаете точно одинаковые планы каждый раз.
источник