本文属于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