SQL 2008执行语句遇到内存不足的案例(error 701)之一

某个特定的存储过程在SQL 2008中执行会遇到如下错误:
Msg 701, Level 17, State 123, Procedure GetAllRevisions_Monthly, Line 22
There is insufficient system memory in resource pool 'internal' to run this query.
Msg 701, Level 17, State 65, Procedure GetAllRevisions_Monthly, Line 22
There is insufficient system memory in resource pool 'internal' to run this query.
sql

  

咱们能够从问题的描述和现象中获得什么信息:windows

首先咱们须要确认几个问题:缓存

1.     这个存储过程在单独执行的时候会不会遇到这个错误。也就是说,在一个没有其余用户访问的时候,单独执行这个存储过程。这一点很是重要,能够帮助咱们确认SQL Server是因为整体的工做负载过高而致使没法分配足够内存执行语句,仍是这条语句自己执行的问题。服务器

2.     这个存储过程是否是每次执行都会遇到这个错误。session

在这个案例中的情形,该存储过程即便在单一用户访问的时候执行,也会遇到这样的错误,而且在每次执行的时候都会出现一样的错。须要注意的是,这个存储过程在第一次执行的时候,会执行一分钟左右之后,报出错误信息。而当第二次执行和之后屡次执行的时候,都是不到一秒当即报错。ide

这里额外的介绍一下对于语句和存储过程,屡次测试的时候须要注意的问题:任何语句在第一次执行的时候,会生成执行计划而且将执行计划缓存的SQL Server的内存中,而第二次或者之后屡次执行,只要以前存放的执行计划没有从内存中清除,语句和存储过程是会重用原有存储的执行计划。所以,若是咱们但愿每次测试都能实现语句第一次执行时候的效果,咱们须要每次执行以前将缓存的执行计划手工清除。DBCC freeprocache这个命令能够帮助咱们清除全部缓存的执行计划。性能

这个案例的测试状况以下:测试

Dbcc freeproccacheui

执行存储过程,用时一分钟,报错。this

再次执行存储过程,马上返回错误。

Dbcc freeproccahe

执行存储过程,用时一分钟,报错。

再次执行存储过程,马上返回错误。

经过这个现象,能够得出结论,首次执行一分钟之后报错,是由于对该存储过程进行了编译和生成执行计划。然后续执行时当即报错,是重用了存储过程缓存的执行计划。也就是说,这个存储过程的解析和编译过程是没有问题的。可是一旦按照编译的执行计划执行的时候,就遇到了内存不足的问题。

以上是咱们经过问题的现象和描述分析出问题的可能性。接下来咱们就进入重现问题收集log的阶段。

  

对于内存不足的错误,咱们须要收集哪些信息用以诊断呢?

Windows性能监视器日志:

SQLServer:Memory

Memory  --这个object描述的是windows的内存分配和可用内存状况

SQL Server 动态性能视图:

sys.dm_exec_query_memory_grants

sys.dm_os_memory_clerks

dbcc memorystatus

如何收集这些信息?

Windows性能监视器日志,只须要在windows性能监视器里面自定义一个用户收集结果集,而后将objectSQLServer:Memory添加进去就能够了。须要主要的是设置收集间隔,因为这个错误在语句开始执行后很快就会出现,所以,将收集间隔设置的过大时是难以准确收集到问题出现时的内存异常状况,咱们建议将收集间隔设置为1-2秒。

收集SQL Server的动态性能视图的方法相似,咱们可使用以下脚本每隔一秒打出一次动态性能视图的结果集合SQL Server的内存状况:

while(1=1)

begin

print getdate()

print '*****sys.dm_os_memory_clerks******'

select * from sys.dm_os_memory_clerks

print '*****sys.dm_exec_query_memory_grants******'

select * from sys.dm_exec_query_memory_grants

print 'DBCC memorystatus'

dbcc memorystatus

waitfor delay '00:00:01'

end

 

management studio中执行这个脚本,这个脚本会每隔一秒打印一次两个view的信息和dbcc 命令的输出。咱们建议设置当前的查询结果输出的文件的方式来输出这个脚本的结果。

 

 

如何为这个问题收集信息?

如今咱们已经知道了如何重现这个问题,也知道了对于这个问题应该收集什么信息来检查,接下来的问题是,如何收集信息?咱们须要在问题出现以前,先讲两个部分信息收集启动:启动咱们本身配置的windows 性能监视器日志,启动脚本。接下来,咱们能够在management studio里面执行语句而且重现问题了。

 

信息的分析

1.     问题出现以前,脚本打出来的dbcc memorystatus

Current Buffer Pool Stats

-----------------------------------

Total Buffers=1048576 (8192MB)

Max Committed=640000 (5000MB)

Committed Target=640000 (5000MB)

Committed=41344 (323MB)

Hashed=6255 (48MB)

Free=17746 (138MB)

Stolen=17343 (135MB)

Reserved=590656 (4614MB)

OutOfMemory=false

WaitingForRM=false

Failed to complete calculation of statistics!                       

Latched=0 (0MB)

Dirty=0 (0MB)

In IO=0 (0MB)

Stolen potential=1 (0MB)

这个信息如何阅读?committed target表示SQL Server能够为buffer pool分配的空间,64000是以8kpage为单位的,所以640000计算出来时5000MBCommitted表示当前已经使用的buffer pool的大小,41344计算出来时323MB。这个信息告诉咱们,在问题出现以前,该系统的buffer pool里面是还有超过4677MB的空闲内存。

2.     Windows event log

在诊断内存问题的时候,首先须要确认的就是,该内存问题是因为windows的内存缺少致使的仍是SQL Server自身的内存问题致使的。当windows遇到内存压力,没有可用内存的时候,OS被强制在其上运行的全部应用程序释放物理内存。SQL Server在这种状况下,因为buffer pool短期以内须要释放大量内存而且急剧缩小大小,当时在SQL Server上执行的语句也会报出错误701,没有足够的内存执行语句。可是这个内存错误的自己是因为windows 强制收回内存致使的。咱们会在另外一个案例中详细讨论这个问题。

咱们检查OS的可用内存,memoryavailable memory(MB),在这个案例中,问题发生以前和问题发生的时候,windows依然保持5.7GB的内存。因此这里的内存问题是SQL Server内部产生的,并非操做系统和服务器的内存缺少问题。

接下来咱们检查SQLServer:Memory 下面的Granted workspace memory(KB),这个值代办SQL Server分配出来执行语句的内存,主要是用来作排序,哈希链接等等。

  

 

经过这个event,咱们发现这里有两次巨大的内存分配(咱们测试的时候执行了两次存储过程),这里的最大值为4617MB。基本上接近了buffer pool中全部的空闲内存。

咱们比较在脚本中收集到的sys.dm_exec_query_memory_grants的信息:

session_id dop request_time             grant_time               requested_memory_kb granted_memory_kb required_memory_kb

---------- --- ------------------------ ------------------------ ------------------- ----------------- ------------------

53         16  02/19/2010 17:13:02.540  02/19/2010 17:13:02.540  4728392             4728392           4728392       

分配的数值和时间上彻底吻合。  

若是咱们遇到的问题是,发现有大量的内存分配给了用户session,可是咱们不知道用户session在当时执行的语句是什么,如何经过动态性能视图来定位语句呢?在sys.dm_exec_query_memory_grants中包含了一个sql_handle的列,咱们能够经过这个列去链接:

Select *,text, query_plan from FROM sys.dm_exec_query_memory_grants

CROSS APPLY sys.dm_exec_sql_text(sql_handle)

CROSS APPLY sys.dm_exec_query_plan (plan_handle)

这样咱们就能够直接经过动态性能视图获得了SQL语句和该SQL语句使用的XML格式的执行计划了。咱们将XML格式的执行计划另存为.sqlplan的后缀的文件,而后在management studio中打开,能够将其展开为图形界面的执行计划。若是不熟悉使用XML格式的执行计划,这种方法能够简化检查执行计划的工做。

问题分析到这里,基本上咱们就能够定位出缘由了,确实是由于这个存储过程执行时,SQL Server预估的内存过大,致使现有内存不足以支持存储过程的执行。

 

解决的方法:

接下来,咱们就能够查看XML的执行计划,在这里,咱们发现SQL Server对这个存储过程使用的并行度为16的执行方式。对于使用并行的语句,SQL Server面对的两个额外的开销:

a.  更多的内存分配

b.  更多的CPU资源消耗。

所以,一旦咱们确认内存使用过多或者CPU使用率太高是跟并行执行有关,咱们须要对SQLServer手工设置一个较低的最大并行度参数:

 

sp_configure 'show advanced options',reconfigure with override

go

sp_configure 'max degree of parallelism',reconfigure with override

上述命令中第一条是用来打开高级选项的,第二条是将最大并行度设置为4.以上设置不须要重启SQL Server服务。关于最大并行度的参数,咱们建议设置为CPU数或者CPU数的一半。在多于32CPU的系统中,咱们建议设置这个值为4或者8.

  

额外的问题:

到目前为止,缘由已经很是清楚了,并且咱们获得的解决问题的方法。这里有一个疑问,为何SQL Server会在明知道没有这么多可用内存的状况下,去生成一个须要这么多内存的执行计划呢?而且还不断的重用这个不能执行的执行计划?这样是SQL Server的产品设计有问题吗?

其实这个问题的出现是一个很巧合的状况。咱们根据SQL Serverdump来分析,发现这个语句在评估和生成执行计划的时候,计算出来所须要的内存接近4670MB,当时buffer pool里面还有4677MB的内存,因此评估出来的执行计划虽然接近了零界值,可是依然是可以知足执行计划的须要的。而当SQL语句真正按照执行计划执行的时候,其实还有一些少许的内存的额外开销,正是由于加上了这些额外的开销,终于超出了剩余的4677MB的内存限制。在这种状况下,SQL Server每次对执行计划的评估和缓存都成功了,而执行的时候才会报错。这也就是为何下次执行的时候该存储过程仍是会重用缓存的执行计划。当语句到执行阶段的时候,无论成功不成功,都不会回头改写缓存的执行计划,因此SQL server就不断的为这个存储过程重用一样的执行计划,直到咱们将最大并行度降为4SQL Server生成新的并行度为4的执行计划之后,该存储过程就能正确的运行了。

相关文章
相关标签/搜索