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),爲了不丟失一些工作過程的心得,所以接下來會寫一下關於雲數據庫的一些使用。

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