1. 程式人生 > >T-SQL執行內幕(7)——記憶體授予

T-SQL執行內幕(7)——記憶體授予

本文屬於SQL Server T-SQL執行內幕系列


    前面提到,在執行過程中,很多操作符都需要記憶體來支援運作。比如Sort操作符,需要儲存所有的輸入以便進行排序,而Hash操作,為了建立大型的hash表,也需要申請資源來儲存資料。

    基於操作符的型別和預估的影響行數及列的大小(這些都可以從統計資訊獲得),執行計劃可以知道這些操作符需要的大概記憶體。整個執行計劃所需的記憶體總和稱為記憶體授予(Memory grant),當大量高開銷的查詢並行執行時,可能會由於記憶體申請超過可用記憶體而出現記憶體溢位。

    為了避免這種情況,SQL Server使用一種叫“資源訊號量(resource semaphore)”來避免這種情況。這個訊號量用於確保所有的記憶體授予總量不會超過伺服器總記憶體。當伺服器的可用記憶體存在壓力時,查詢所需的額外記憶體申請就必須等待直到其他已經持有記憶體資源的查詢完成並釋放資源為止。

    當前的記憶體授予情況可以查詢sys.dm_exec_query_memory_grants檢視。如果查詢需要等待記憶體授予,會產生一個事件“Execution Warnings Event Class”,這個事件針對語句或儲存過程,監控查詢是否在處理前等待記憶體或者在初始化前獲取記憶體失敗。

    但是當深入研究時,你會發現情況更加複雜,查詢並不是在一開始就申請全部記憶體,它們可以申請少量的足以支援開始的資源即可。當授予記憶體較少時,操作符會被告知需要調整執行時間以符合分配的記憶體資源。

    另外,前面提到記憶體授予的計算來源是基於統計資訊,如果統計資訊不準確,那麼會導致申請的記憶體嚴重不符,過量或不足都會出問題,如果過量,則導致浪費,且因為可用記憶體不足而影響其他查詢。這種情況在本人工作過程中很常見。如果過少,由於本查詢不夠記憶體進行操作,很多操作符如排序、hash等必須拆分到TempDB執行,意味著在磁碟操作,效能會下降很嚴重,相關的警告事件有:

    嚴格來說,hash會更加複雜, Hash Warning SQL Profiler Event ,也可以看一下本人文章: SQL Server Hash Warning 優化

    記憶體授予到一個查詢實際上是“預定(reservation)”而不是“分配(allocation)”,查詢執行時才實際用到這些預定的記憶體,現實環境中確實存在實際執行時所用記憶體比申請的少,這時記憶體的實際消耗小於預留的量,但是大量預定記憶體會因為資源訊號量的限制(資源訊號量記錄了預定記憶體,認為剩餘記憶體不夠)其他查詢。

    還有一個相關概念:查詢編譯資源訊號量。適用於查詢編譯而不是查詢執行。通常情況下(特別是OLTP型別系統),編譯和重編譯都相對少的時候,這個並不是什麼問題,但是如果出現大量這種情況時,就可能意味著查詢計劃重用出現異常:2.0 Diagnosing Plan Cache Related Performance Problems and Suggested Solutions

    最後一個問題是:記住不是所有的查詢都需要記憶體授予,記憶體授予僅用於包含排序、大型掃描(並行)和hash join/aggregates(如未排序輸入,可能會使用streaming aggregates)的查詢。如果你看到在不允許高延時的系統(如網站)系統中看到記憶體授予問題,是時候檢查資料模型設計。因為記憶體授予的應用場景應該在允許延時的分析型系統。

    更多資訊詳見:Understanding SQL server memory grant,由於時間關係這裡摘取了一些有用程式碼:

    1. 查詢使用最多記憶體授予的語句:

SELECT mg.granted_memory_kb, mg.session_id, t.text, qp.query_plan 
FROM sys.dm_exec_query_memory_grants AS mg
CROSS APPLY sys.dm_exec_sql_text(mg.sql_handle) AS t
CROSS APPLY sys.dm_exec_query_plan(mg.plan_handle) AS qp
ORDER BY 1 DESC OPTION (MAXDOP 1)

    2. 從快取中查詢帶有記憶體授予的資訊:

SELECT t.text, cp.objtype,qp.query_plan
FROM sys.dm_exec_cached_plans AS cp
JOIN sys.dm_exec_query_stats AS qs ON cp.plan_handle = qs.plan_handle
CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS t
WHERE qp.query_plan.exist('declare namespace n="http://schemas.microsoft.com/sqlserver/2004/07/showplan"; //n:MemoryFractions') = 1

    3. 擴充套件事件:注意修改碟符和資料夾名    

1. 監控hash_warning和sort_warning:
	create event session [TempDB Spills] 
	on server 
	add event sqlserver.hash_warning 
	( 
	    action ( sqlserver.session_id, sqlserver.plan_handle, sqlserver.sql_text ) 
	    where ( sqlserver.is_system=0 ) 
	), 
	add event sqlserver.sort_warning 
	( 
	    action ( sqlserver.session_id, sqlserver.plan_handle, sqlserver.sql_text ) 
	    where ( sqlserver.is_system=0 ) 
	) 
	add target package0.event_file 
	( set filename='c:\ExtEvents\TempDB_Spiils.xel', max_file_size=25 ), 
	add target package0.ring_buffer 
	( set max_memory=4096 ) 
	with  -- Extended Events session properties 
	( 
	    max_memory=4096KB 
	    ,event_retention_mode=allow_single_event_loss 
	    ,max_dispatch_latency=15 seconds 
	    ,track_causality=off 
	    ,memory_partition_mode=none 
	    ,startup_state=off 
	); 
	-- Starting Event Session 
	alter event session [TempDB Spills] on server state=start; 
	-- Stopping Event Session 
	alter event session [TempDB Spills] on server state=stop; 
	-- Dropping Event Session 
	drop event session [TempDB Spills] on server; 
	--從檔案中檢視結果:
	;with TargetData(Data, File_Name, File_Offset) 
	as 
	( 
	    select convert(xml,event_data) as Data, file_name, file_offset 
	from sys.fn_xe_file_target_read_file('c:\extevents\TempDB_Spiils*.xel', null, null
	    ,null) 
	) 
	,EventInfo([Event Time], [Event], SPID, [SQL], PlanHandle, File_Name, File_Offset) 
	as 
	( 
	    select 
	        Data.value('/event[1]/@timestamp','datetime') as [Event Time] 
	        ,Data.value('/event[1]/@name','sysname') as [Event] 
	        ,Data.value('(/event[1]/action[@name="session_id"]/value)[1]','smallint') as [SPID] 
	,Data.value('(/event[1]/action[@name="sql_text"]/value)[1]','nvarchar(max)') 
	    as [SQL] 
	        ,Data.value('xs:hexBinary((/event[1]/action[@name="plan_handle"]/value)[1])' 
	            ,'varbinary(64)') as [PlanHandle] 
	        ,File_Name, File_Offset 
	    from TargetData 
	) 
	select ei.[Event Time], ei.File_Name, ei.File_Offset, ei.[Event], ei.SPID, ei.SQL
	    ,qp.Query_Plan 
	from EventInfo ei outer apply sys.dm_exec_query_plan(ei.PlanHandle) qp 
	--直接檢視
	;WITH TargetData (Data)
	AS (
		SELECT convert(XML, st.target_data) AS Data
		FROM sys.dm_xe_sessions s
		JOIN sys.dm_xe_session_targets st ON s.address = st.event_session_address
		WHERE s.NAME = 'TempDB Spills'
			AND st.target_name = 'ring_buffer'
		)
		,EventInfo (
		[Event Time]
		,[Event]
		,SPID
		,[SQL]
		,PlanHandle
		)
	AS (
		SELECT t.e.value('@timestamp', 'datetime') AS [Event Time]
			,t.e.value('@name', 'sysname') AS [Event]
			,t.e.value('(action[@name="session_id"]/value)[1]', 'smallint') AS [SPID]
			,t.e.value('(action[@name="sql_text"]/value)[1]', 'nvarchar(max)') AS [SQL]
			,t.e.value('xs:hexBinary((action[@name="plan_handle"]/value)[1])', 'varbinary(64)') AS [PlanHandle]
		FROM TargetData
		CROSS APPLY TargetData.Data.nodes('/RingBufferTarget/event') AS t(e)
		)
	SELECT ei.[Event Time]
		,ei.[Event]
		,ei.SPID
		,ei.SQL
		,qp.Query_Plan
	FROM EventInfo ei
	OUTER APPLY sys.dm_exec_query_plan(ei.PlanHandle) qp

    4. 分頁事件:

分頁:
create event session PageSplits_Tracking 
on server 
add event sqlserver.transaction_log 
( 
    where operation = 11  -- lop_delete_split 
        and database_id = 17 
) 
add target package0.histogram 
( 
    set 
        filtering_event_name = 'sqlserver.transaction_log', 
        source_type = 0, -- event column 
        source = 'alloc_unit_id' 
) 
;with Data(alloc_unit_id, splits) 
as 
( 
    sel ect c.n.value('(value)[1]', 'bigint') as alloc_unit_id, c.n.value('(@count)[1]'
,'bigint') as splits 
    from 
    ( 
        select convert(xml,target_data) target_data 
        from sys.dm_xe_sessions s with (nolock) join sys.dm_xe_session_targets t on 
            s.address = t.event_session_address 
        where s.name = 'PageSplits_Tracking' and t.target_name = 'histogram' 
    ) as d cross apply 
        target_data.nodes('HistogramTarget/Slot') as c(n) 
) 
select 
    s.name + '.' + o.name as [Table], i.index_id, i.name as [Index] 
    ,d.Splits, i.fill_factor as [Fill Factor] 
from 
    Data d join sys.allocation_units au with (nolock) on 
        d.alloc_unit_id = au.allocation_unit_id 
    join sys.partitions p with (nolock) on 
        au.container_id = p.partition_id 
    join sys.indexes i with (nolock) on 
        p.object_id = i.object_id and p.index_id = i.index_id 
    join sys.objects o with (nolock) on 
        i.object_id = o.object_id 
    join sys.schemas s on 
        o.schema_id = s.schema_id