此文描述查询内存授予(query memory grant)在SQL Server上是如何工作的,适用于SQL 2005 到2008。
查询内存授予(下文缩写为QMG)是用于存储当数据进行排序和连接时的临时中间数据行。查询在实际执行前需要先请求保留内存,所以会存在一个授予的动作。
这样的好处是提高查询的可靠性和避免单个查询占用所有的内存。
SQL Server在收到查询时,会执行3个被定义好的步骤来返回用户所请求的结果集。
1.生成编译计划。它包括各种逻辑指令,如怎么联接数据行。
2.生成执行计划,它包含将编译计划中的各种逻辑引用转换成实际的对象的指令和查询执行的跟踪机制。
3. 从指令树的顶端开始执行。
生成编译计划是件开销较大的事情,因为它需要在数以百计的编译计划中挑出较优的一个。每一个编译计划都能被缓存,并被多个执行计划共享使用。
内存授予预估的有些参数被存储在编译计划当中,并且它本身有一个机制用于计算在实际执行时需要授予给查询的内存大小。
内存使用者(Memory Consumers)
一个成功执行的查询包括三个主要的内存使用者:编译,缓存和内存授予。
编译:创建和在数以百计的计划当找到最优的编译计划是件很耗内存的事。它的时间通常很短,因为优化器会在找到最优的编译计划后便马上释放内存。
编译主要使用内存和CPU资源。缺少可用内存可能会导致编译延迟和得到非最优的编译计划。
缓存: 为了编译计划重用,SQL Server会把编译计划保存到缓存中。它会长时间占用内存。如果缺少可用内存会导致不必要的重编译。
内存授予:这部分主要用于存储当数据进行排序和连接时的临时中间数据行。缺少可用内存会导致查询使用硬盘来暂存数据,降低查询性能。
SQL Server使用内存代理(Memory Broker)来平衡三个使用者对内存的使用。基于使用者需要的内存和可用物理内存,如果内存代理预估到内存不足时,便会设定内存使用限制并通知三者削减各自内存使用量。
授予参数(Grant Parameters)
当SQL Server创建编译计划时,会计算两个参数:必须内存(Requeried memory)和额外内存(Additional memory)。
必须内存:执行排序和哈希联接所需的最少内存。这部分内存是“必须”的,它用来创建处理排序和哈希所需要的内部数据结构。
额外内存:存储所有临时数据行所需的内存。它的大小由基数评估(Cardinality estimate,如行数和行大小)决定。“额外”,顾名思义在缺少这部分内存时,将会将临时数据行存到硬盘上,并不会导致查询失败。
一个查询的额外内存大小如果超过预设的限制,它实际得到的内存量并不一定会跟请求量一样。
例如,对行大小为10byte的100万行数据进行排序,此查询的必须内存为为512KB(此值是SQL Server处理一个排序操作创建内部数据结构所需的最小内存量)。为了存储所有数据行,额外内存可能是10MB。
当编译计划中含有多个排序和联接操作时,额外内存的计算就变得复杂了。因为SQL Server要考虑所有操作符如何高效地使用内存。可以查看ShowPlan XML中的<MemoryFractions>标记部分内容,获取更多内存使用的信息。
并行度依赖(DOP Dependence)
当SQL Server使用多个CPU时,可以将查询分解到到多个工作者线程并行执行来提高性能。这些工作者线程相互独立运行,然后使用并行操作符(Parallelism operator a.k.a Exchange)传输数据。
并行模式增加了内存的使用,因为并行的每个工作者都 需要一份排序和联接的数据副本并且并行操作符需要缓存所有被传输的临时数据。因此DOP N会使用N倍所需的内存。
然而,需要处理的数据行数和存储它们的内存量并不会随着DOP的改变而改变。也就是说,额外内存不会因DOP面改变。从SQL 2008开始并行操作符使用的缓存内存也被算成必须内存授予。
内存授予过程(Memory grant process)
SQL Server需要考虑服务器内存量和并发查询数量来避免最终提交的内存量超过物理内存限制.这分两步完成,首先它计算需要分配多少内存给某个查询,然后使用内部因子-资源信号量(Resource Semaphore)去保留实际内存,或者当过多查询请求内存时,它会调节(Throttle)这些请求.按照下面的步骤来确定内存请求量:
1. SQL Server根据服务器状态和计划来确定最大并行度.
2. SQL Server检查是否需要执行内存授予,如果不需要则马上执行查询.例如查询中没有group by 或order by,则不需要进行内存授予.
3. 为单个查询计算内存限制,默认值=0.25(32bit SQL 2000为0.2)*0.9*Server内存.这个比值在SQL 2008是可配置的.单个查询的内存限制,避免了一个查询占用所用服务器内存.
4. 计算理想情况下必须的查询内存量=理想内存需求量*DOP+额外内存(+Exchange on SQL 2008).
5. 然后检查理想内存理是否超过单个查询内存限制.如果超过,则会减少额外内存量,直到符合单个查询内存限制.这个修正后的内存称为被请求的内存(Requested memory),SQL Server调用资源信号去授予这个被请求的内存.
资源信号量(Resource Semaphore)
资源信号量负责调整和满足内存授予请求,以保证它们的使用量在服务器的限制内.
1. 只当有足够空闲内存时,资源信号才允许查询保留内存.否则将会被排进一个等待队列.
2. 当资源信号量收到一个查询请求时,会先检查等待队列中是否有正在等待的请求,如果有,则此请求也会被放进此FIFO的队列中.
3. 当队列中无等待的查询或者之前的查询返回被保留的内存时,资源信号会尝试授予内存。
4. 如果找到等待的查询时,请求的查询将会被入进队列中。
5. 如果没有等待的查询,它会检查可用空闲内存。
6. 如果找到足够的空闲内存,则授予查询所请求的内存。查询就可以开始运行了。
7. 如果没有找到足够的空闲内存,则当前查询会被放进等待队列。
8. 当有足够的可用空闲内存时,资源信号量会唤醒队列中等待的查询。
调试内存授予相关的问题
SQL Server 提供了一些DMV用于分析内存授予相关的问题。
sys.dm_exec_query_resource_semaphores
此视图返回当前资源信号量的状态。它返回两行,一行叫常规资源信号量(max_target_memory_kb列为非NULL),一行叫小型资源信号量(小于5MB的查询)。
sys.dm_exec_query_memory_grants
返回已经获得内存授予和正在等待内存授予的查询相关信息。正在等待的查询的grant_time列会是NULL。资源信号量使用内部查询成本来评估授予内存的先后次序。
is_next_candidate列标识出下一下被唤醒的正在等待的查询。
sys.dm_os_wait_stats
此DMV返回显示所有服务器对象的等待统计信息。内存授予的等待类型叫做“RESOURCE_SEMAPHORE”.如果此等待类型过多,则可能是大型查询引起的。
使用示例
查询内存队列中正在等待的查询:
SELECT * FROM sys.dm_exec_query_memory_grants where grant_time is null
查询谁使用最多查询内存授予:
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)
查询缓存中使用查询内存授予的查询:
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
总结:
1. 原文地址:Understanding SQL server memory grant
2. 我是在看过ShanksGao的这篇博文后,找到这个资料脑补的.
3. 大型查询,特别是大型统计查询,肯定会有排序和哈希操作,而且是最消耗资源的操作。理解查询内存授予有助于判断必要性和优化这种查询。