SQL Server 列存储索引性能总结(12)——RESOURCE_SEMAPHORE 等待

SQL Server 列存储索引性能总结(10)——行组的大小影响一文中,实验过程当把服务器的max server memory(MB)值调到300MB并创建聚集列存储所以时,整整跑了5个小时都还没完成,然后查看对应的会话阻塞情况,发现了这个我过去很少见到过的等待状态,所以找了一些资料了解一下。当然,光从行为上,我大概能猜出是内存不够的原因,但是还是需要了解一下具体情况。

背景

  在 “SQL Server 列存储索引性能总结(10)——行组的大小影响”一文的实验过程中,在把可用内存调到300MB之后,出现了很多问题,首先创建索引很慢,还有出现服务器启动不了,还有就是重启后数据库直接置疑等情况。
   在创建很慢得时候,通过sys.sysprocesses查看对应的SPID,发现等待状态为RESOURCE_SEMAPHORE,这个等待状态比较少见,毕竟内存只有300MB的系统几乎不会出现在正式环境下。基于好奇,我整理了一下资料并分享一下。

RESOURCE_SEMAPHORE等待

   这个等待类型发生在一个查询所需内存由于其他并发查询的影响,不能马上被授予(grant)的时候。这个等待信息如果处于非常高的出现频率及其时间很长的话,从定义里面分析可知有两种可能:

  1. 过度的内存请求量,也有可能是本身内存不足。
  2. 过度的并发量。
       抛开这次把内存降低导致的“内存不足”的可能,在正式环境下更多的意味着资源与负载不对等,或者存在低效语句导致内存申请不合理。这个等待类型的出现几乎必然会导致所有数据库用户的使用感受降低,需要尽早干预。
       出现这个等待类型的时候,应该关联Memory Grants Pending(SQLServer:Memory Manager\Memory Grants Pending)和Memory Grants Outstanding(SQLServer:Memory Manager\Memory Grants Outstanding)计数器或者使用SQL Server 列存储索引性能总结(9)——重建和重组聚集列存储索引所需的内存中提到的内存请求量的查询来检查一下当前的服务器状态:
SELECT cast(requested_memory_kb / 1024. as Decimal(9,2)) as RequestedMemoryMB
	,cast(granted_memory_kb / 1024. as Decimal(9,2)) as GrantedMemoryMB
	,cast(required_memory_kb / 1024. as Decimal(9,2)) as RequiredMemoryMB
	,cast(used_memory_kb / 1024. as Decimal(9,2)) as UsedMemoryMB
	,cast(max_used_memory_kb / 1024. as Decimal(9,2)) as MaxUsedMemoryMB
	FROM sys.dm_exec_query_memory_grants;

   RESOURCE_SEMAPHORE 处于高值的话,意味着可能有内存压力,特别是Memory Grants Pending不是0。
   RESOURCE_SEMAPHORE是SQL Server的内部机制,允许查询在足够内存的前提下预留内存,否则查询会被强制放入等待队列中(这也是为什么我的语句跑了5个小时,其实一直处于等待状态)。
   当SQL Server收到了新的查询请求,会先检查是否又等待队列。如果有,就会把请求加到队列后面,基于先进先出的原则继续等待。在队列中的查询的等待状态就会显示为RESOURCE_SEMAPHORE。一旦可用内存已经可以满足查询需要,就会授予队列中第一个查询所需内存,然后让其执行,并更新队列状态。

根源

  当有高内存消耗的查询出现,比如需要排序或者Hash,但是申请的内存不能被满足时,就会出现这种等待。
   如果在物理内存和配置内存都合理的情况下,那就应该时优化内存。当然如果资源与负载不对等(就是我的例子),页应该对应地增加内存资源或者其他可能的资源。

解决方案

   其实分析来看,这个等待状态主要就是资源不足,只是资源不足的原因可能时自身的也可能时外部的。所以除非你的硬件资源确实不足,否则可以考虑以下的解决方案(按顺序):

  1. 确保统计信息的实时性。
  2. 使用Trace、Extended Event、Performance Counters来检查资源及高内存消耗的查询。
  3. 优化索引,特别是对具有大型排序或Hash Join的操作。
  4. 重写SQL,避免UNION/DISTINCT/ORDER BY操作。
  5. 增加内存。

   同时也可以使用sys. dm_exec_query_resource_semaphores这个DMV来查看当前情况。
   对于我个人而言,我比较喜欢使用SQL或者xevent来查看高内存消耗的查询,SQL 的话有很多工具,比如sp_whoisactive,可惜Azure SQL DB不能使用。扩展事件(Extented Event)的话,可以参考一下:

CREATE EVENT SESSION [MemoryUsage] ON SERVER
ADD EVENT sqlserver.query_memory_grant_usage
(
ACTION
(
sqlserver.query_hash
,sqlserver.query_plan_hash
,sqlserver.sql_text
)
WHERE granted_memory_kb > 2048 --2 MB
)
ADD TARGET package0.ring_buffer(SET max_events_limit=(0 /*unlimited*/),max_memory=(1048576 /*1 GB*/))
WITH (STARTUP_STATE=OFF,MAX_DISPATCH_LATENCY = 5SECONDS)

IF OBJECT_ID('tempdb..#capture_waits_data') IS NOT NULL
DROP TABLE #capture_waits_data
SELECT CAST(target_data as xml) AS targetdata
INTO #capture_waits_data
FROM sys.dm_xe_session_targets xet
JOIN sys.dm_xe_sessions xes
ON xes.address = xet.event_session_address
WHERE xes.name = 'MemoryUsage'
AND xet.target_name = 'ring_buffer';
--*/
/**********************************************************/
SELECT xed.event_data.value('(@timestamp)[1]', 'datetime2') AS datetime_utc,
DATEADD(hh,DATEDIFF(hh, GETUTCDATE(), CURRENT_TIMESTAMP),xed.event_data.value('(@timestamp)[1]', 'datetime2')) AS datetime_local,
xed.event_data.value('(@name)[1]', 'varchar(50)') AS event_type,
xed.event_data.value('(data[@name="ideal_memory_kb"]/value)[1]', 'bigint') AS ideal_memory_kb,
xed.event_data.value('(data[@name="granted_memory_kb"]/value)[1]', 'bigint') AS granted_memory_kb,
xed.event_data.value('(data[@name="used_memory_kb"]/value)[1]', 'bigint') AS used_memory_kb,
xed.event_data.value('(data[@name="usage_percent"]/value)[1]', 'int') AS usage_percent,
xed.event_data.value('(data[@name="dop"]/value)[1]', 'int') AS dop,
xed.event_data.value('(data[@name="granted_percent"]/value)[1]', 'int') AS granted_percent,
xed.event_data.value('(action[@name="sql_text"]/value)[1]', 'varchar(max)') AS sql_text,
xed.event_data.value('(action[@name="query_plan_hash"]/value)[1]', 'numeric(20)') AS query_plan_hash,
xed.event_data.value('(action[@name="query_hash"]/value)[1]', 'numeric(20)') AS query_hash
FROM #capture_waits_data
CROSS APPLY targetdata.nodes('//RingBufferTarget/event') AS xed (event_data)
WHERE 1=1
--/* Search for large memory grants.
AND xed.event_data.value('(data[@name="used_memory_kb"]/value)[1]', 'bigint') > 5120 -- 5MB
--*/
--/* Search for grants too large for the actual used
AND xed.event_data.value('(data[@name="usage_percent"]/value)[1]', 'bigint') < 50
--*/

   由于工作原因,暂时对列存储没有使用得太深入,所以本系列暂时到此为止,不过后续有内容更新也会继续下去。接下来我所在的项目要开始Azure上的SQL DB和SQL DW(现在叫Azure Synapse Analytics),为了不丢失一些工作过程的心得,所以接下来会写一下关于云数据库的一些使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章