原文:SQL 2008执行语句遇到内存不足(1)——error 701

转自:http://blogs.msdn.com/b/apgcdsd/archive/2011/01/17/sql-2008-error-701.aspx

某个特定的存储过程在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.

我们可以从问题的描述和现象中得到什么信息:

首先我们需要确认几个问题:

1.     这个存储过程在单独执行的时候会不会遇到这个错误。也就是说,在一个没有其他用户访问的时候,单独执行这个存储过程。这一点非常重要,可以帮助我们确认SQL Server是由于总体的工作负载太高而导致无法分配足够内存执行语句,还是这条语句本身执行的问题。

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

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

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

这个案例的测试情况如下:

Dbcc freeproccache

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

再次执行存储过程,立刻返回错误。

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是以8k的page为单位的,因此640000计算出来时5000MB。Committed表示当前已经使用的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的可用内存,memory:available 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',1 reconfigure with override

go

sp_configure 'max degree of parallelism',4 reconfigure with override

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

额外的问题:

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

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

SQL 2008执行语句遇到内存不足(1)——error 701的更多相关文章

  1. .net core 下监控Sql的执行语句

    原文:.net core 下监控Sql的执行语句 最近在编写.net core程序,因为数据库从Sql Server 切换到 MySql的原因,无法直接查看sql的具体语句,随着业务量的剧增,痛苦也与 ...

  2. 拼接SQL执行语句时,对单引号的处理

    例: declare @SQL nvarchar(1000); declare @str nvarchar(100); set @str='Joe''s NB'; // 打印出来的应该是这样:Joe' ...

  3. SQL SERVER 执行计划各字段注释

    SET SHOWPLAN_ALL使 Microsoft® SQL Server™ 不执行 Transact-SQL 语句.相反,SQL Server 返回有关语句执行方式和语句预计所需资源的详细信息. ...

  4. 45、SQL逻辑查询语句执行顺序

    一 SELECT语句关键字的定义顺序 SELECT DISTINCT <select_list> FROM <left_table> <join_type> JOI ...

  5. 第四篇:记录相关操作 SQL逻辑查询语句执行顺序

    http://www.cnblogs.com/linhaifeng/articles/7372774.html 一 SELECT语句关键字的定义顺序 SELECT DISTINCT <selec ...

  6. 9、SQL逻辑查询语句执行顺序

    本篇导航: SELECT语句关键字的定义顺序 SELECT语句关键字的执行顺序 准备表和数据 准备SQL逻辑查询测试语句 执行顺序分析 一.SELECT语句关键字的定义顺序 SELECT DISTIN ...

  7. SQL逻辑查询语句执行顺序 需要重新整理

    一.SQL语句定义顺序 1 2 3 4 5 6 7 8 9 10 SELECT DISTINCT <select_list> FROM <left_table> <joi ...

  8. mysql SQL 逻辑查询语句和执行顺序

    关键字的执行优先级(重点) fromwheregroup byhavingselectdistinctorder bylimit 先创建两个表 CREATE TABLE table1 ( custom ...

  9. sql逻辑查询语句的执行顺序

    SELECT语句关键字的定义顺序 SELECT DISTINCT <select_list> FROM <left_table> <join_type> JOIN ...

随机推荐

  1. The mell hall——坑爹

    The mell hall 题目描述 In HUST,there are always manystudents go to the mell hall at the same time as soo ...

  2. 《深入理解mybatis原理》 Mybatis初始化机制具体解释

    对于不论什么框架而言.在使用前都要进行一系列的初始化,MyBatis也不例外. 本章将通过下面几点具体介绍MyBatis的初始化过程. 1.MyBatis的初始化做了什么 2. MyBatis基于XM ...

  3. 该Tiled地图制作拿到项目~~这是偷懒,为了直接复制后写来

    1.现在,.h声明private: cocos2d::CCSprite* ninja; cocos2d::CCTMXTiledMap*  tileMap; 然后.cpp中增加tileMap = CCT ...

  4. POJ1300(欧拉回路)

    Door Man Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 2139   Accepted: 858 Descripti ...

  5. Android4.4 Framework分析——Zygote进程的启动过程

    Android启动过程中的第一个进程init.在启动过程中会启动两个关键的系统服务进程ServiceManager和Zygote. 本文要介绍的就是Zygote进程的启动,Zygote俗称孵化器,专门 ...

  6. uva live 4394 String painter 间隔dp

    // uva live 4394 String painter // // 问题是,在培训指导dp运动主题,乍一看,我以为只是一点点复杂 // A A磕磕磕,两个半小时后,.发现超过例子.然而,鉴于他 ...

  7. Could not load file or assembly&#39;System.Data.SQLite.dll&#39; or one of its depedencies

    [问题]  在我本机的开发环境c#连接sqlite3没有问题,但是release版本号移植到其它的机器就提示Could not load file or assembly'System.Data. ...

  8. WPF 3D:MeshGeometry3D的定义和光照

    原文 WPF 3D:MeshGeometry3D的定义和光照 由于WPF计算光照会根据整个平面的方向向量,所以如果在不同面上使用同一个点可能会达到不同的光照效果.让我们用不同的定义Mesh的方法来演示 ...

  9. 发布Ubuntu/Linux系统cache,增加可用内存空间

    桌面Ubuntu总内存4G,但free只有内存有100M 重视top命令检查看到真正的能力free内存.以下是真正的内存使用情况的看法有一个命令. watch -n 1 cat /proc/memin ...

  10. ADO.NET连接方式

    使用Command.DataReader和DataSet两种方法实现数据绑定 方法1:使用Command和DataReader SqlConnection con = new SqlConnectio ...