查询内存溢出

首先我们来看一个带排序的查询,点击工具栏的显示包含实际的执行计划。

1 SELECT * FROM AdventureWorks2008R2.Person.Person WHERE FirstName LIKE ‘w%‘ ORDER BY 1

从执行计划里可以看出,SELECT运算符包含了内存授予(Memory Grant)信息(一般情况下不会出现,这里是因为我们的语句包含排序操作)。内存授予是KB为单位,是当执行计划中的一些运算符(像Sort/Hash等运算符)的执行,需要使用内存来完成——因此也被称为查询内存(Query Memory)

在查询正式执行前,查询内存必须被SQL Server授予才可以。对于提供的查询,查询优化器根据查询对象的对应统计信息来决定需要多少查询内存。现在的问题就是,当统计信息过期了,SQL Server就会低估要处理的行数。在这个情况下,SQL Server对于提供的查询还是会请求更少的查询内存。但当查询真正开始后,SQL Server就不能改变授予的内存大小,也不能请求更多的内存。查询必须在授予的查询内存里完成操作。在这个情况下,SQL Server需要把Sort/Hash运算符涌进TempDb,这就意味我们原先在内存里快速操作变成物理磁盘上慢速操作。SQL Server Profiler可以通过Sort WarningsHash Warning这2个事件来跟踪查询内存溢出(Query Memory Spills)。

很遗憾在SQL SERVER 2008(R2)没有提供这样的扩展事件来跟踪内存溢出事件。在SQL Server 2012里才有来解决这个问题。在这个文章里我会向你展示一个非常简单的例子,由于统计信息过期,你是如何产生内存溢出(Query Memory Spills)。我们来创建一个新的数据库,在里面创建一个表:

 1 SET STATISTICS IO ON
 2 SET STATISTICS TIME ON
 3 GO
 4
 5 -- Create a new database
 6 CREATE DATABASE InsufficientMemoryGrants
 7 GO
 8
 9 USE InsufficientMemoryGrants
10 GO
11
12 -- Create a test table
13 CREATE TABLE TestTable
14 (
15    Col1 INT IDENTITY PRIMARY KEY,
16    Col2 INT,
17    Col3 CHAR(4000)
18 )
19 GO
20
21 -- Create a Non-Clustered Index on column Col2
22 CREATE NONCLUSTERED INDEX idxTable1_Column2 ON TestTable(Col2)
23 GO

TestTable表包含第1列的主键,第2列的非聚集索引,第3列的CHAR(4000)列。接下来我们要用第3列来做ORDER BY,因此在执行计划里,查询优化器必须生成明确的排序运算符。下一步我会往表里插入1500条记录,表里数据的所有值在第2列会平均分布——在表里每个值只出现一次。

 1 -- Insert 1500 records
 2 DECLARE @i INT = 1
 3 WHILE (@i <= 1500)
 4 BEGIN
 5     INSERT INTO TestTable VALUES
 6     (
 7          @i ,
 8         REPLICATE(‘x‘,4000)
 9     )
10
11     SET @i += 1
12 END
13 GO

有了这样的数据准备,我们可以执行一个简单的查询,会在执行计划里好似用独立的排序运算符:

1 DECLARE @x INT
2
3 SELECT @x = Col2 FROM TestTable
4 WHERE Col2 = 2
5 ORDER BY Col3
6 GO

当我们在SQL Server Profiler里尝试跟踪Sort WarningsHash Warning这2个事件时,会发现跟踪不到。

你也可以使用DMV sys.dm_io_virtual_file_stats,看下num_of_writes列和num_of_bytes_written列,来看下刚才查询在TempDb是否有活动。当然,这个只有你一个人在使用当前数据库时有效。

 1 -- Check the activity in TempDb before we execute the sort operation.
 2 SELECT num_of_writes, num_of_bytes_written FROM
 3 sys.dm_io_virtual_file_stats(DB_ID(‘tempdb‘), 1)
 4 GO
 5
 6 -- Select a record through the previous created Non-Clustered Index from the table.
 7 -- SQL Server retrieves the record through a Non-Clustered Index Seek operator.
 8 -- SQL Server estimates for the sort operator 1 record, which also reflects
 9 -- the actual number of rows.
10 -- SQL Server requests a memory grant of 1024kb - the sorting is done inside
11 -- the memory.
12 DECLARE @x INT
13
14 SELECT @x = Col2 FROM TestTable
15 WHERE Col2 = 2
16 ORDER BY Col3
17 GO
18
19 -- Check the activity in TempDb after the execution of the sort operation.
20 -- There was no activity in TempDb during the previous SELECT statement.
21 SELECT num_of_writes, num_of_bytes_written FROM
22 sys.dm_io_virtual_file_stats(DB_ID(‘tempdb‘), 1)
23 GO

可以发现,查询执行前后没有任何改变。这个查询在我的系统里花费了1毫秒。

现在我们有了1500条记录的表,这就是说我们需要修改20% + 500的数据行才可以触发SQL Server来更新统计信息。我们来计算下,就可以知道我们需要需要修改800条行数据(500 + 300)。因此让我们来插入第2列值为2的799条数据。这样我们就改变了数据的分布情况,当SQL Server还是不会更新统计信息,因为还有一条数据没有更新,直到这条数据更新了才会触发SQL Server内部的统计信息自动更新!

我们再次执行刚才的查询:

 1 -- Check the activity in TempDb before we execute the sort operation.
 2 SELECT num_of_writes, num_of_bytes_written FROM
 3 sys.dm_io_virtual_file_stats(DB_ID(‘tempdb‘), 1)
 4 GO
 5
 6 -- Select a record through the previous created Non-Clustered Index from the table.
 7 -- SQL Server retrieves the record through a Non-Clustered Index Seek operator.
 8 -- SQL Server estimates for the sort operator 1 record, which also reflects
 9 -- the actual number of rows.
10 -- SQL Server requests a memory grant of 1024kb - the sorting is done inside
11 -- the memory.
12 DECLARE @x INT
13
14 SELECT @x = Col2 FROM TestTable
15 WHERE Col2 = 2
16 ORDER BY Col3
17 GO
18
19 -- Check the activity in TempDb after the execution of the sort operation.
20 -- There was no activity in TempDb during the previous SELECT statement.
21 SELECT num_of_writes, num_of_bytes_written FROM
22 sys.dm_io_virtual_file_stats(DB_ID(‘tempdb‘), 1)
23 GO

SQL Server就会把排序运算符涌进TempDb,因为SQL Server只申请了1K的查询内存授予(Query Memory Grant),它的估计行数是1——内存授予和刚才的一样。

DMV sys.dm_io_virtual_file_stats 显示在TempDb里有活动,这是SQL Server把排序运算符涌进TempDb的证据。

SQL Server Profiler也显示了Sort Warning的事件。

我们检查下执行计划里的估计行数(Estimated Number of Rows),和实际行数(Actual Number of Rows)完全不一样。

这里的执行时间花费了184毫秒,和刚才的1毫秒完全不一样。

现在我们往表里再插入1条记录,再次执行查询,一切正常,因为SQL Server会触发统计信息更新并正确估计查询内存授予(Query Memory Grant):

 1 -- Insert 1 records into table TestTable
 2 SELECT TOP 1 IDENTITY(INT, 1, 1) AS n INTO #Nums
 3 FROM master.dbo.syscolumns sc1
 4
 5 INSERT INTO TestTable (Col2, Col3)
 6 SELECT 2, REPLICATE(‘x‘, 2000) FROM #nums
 7 DROP TABLE #nums
 8 GO
 9
10 -- Check the activity in TempDb before we execute the sort operation.
11 SELECT num_of_writes, num_of_bytes_written FROM
12 sys.dm_io_virtual_file_stats(DB_ID(‘tempdb‘), 1)
13 GO
14
15 -- SQL Server has now accurate statistics and estimates 801 rows for the sort operator.
16 -- SQL Server requests a memory grant of 6.656kb, which is now enough.
17 -- SQL Server now spills the sort operation not to TempDb.
18 -- Logical reads: 577
19 DECLARE @x INT
20
21 SELECT @x = Col2 FROM TestTable
22 WHERE Col2 = 2
23 ORDER BY Col3
24 GO
25
26 -- Check the activity in TempDb after the execution of the sort operation.
27 -- There is now no activity in TempDb during the previous SELECT statement.
28 SELECT num_of_writes, num_of_bytes_written FROM
29 sys.dm_io_virtual_file_stats(DB_ID(‘tempdb‘), 1)
30 GO

嗯,这是个非常简单的例子,向你展示在SQL Server内部如何产生Sort Warning,其实一点也不神秘!

时间: 2024-10-10 09:12:22

查询内存溢出的相关文章

php查询mysql返回大量数据结果集导致内存溢出的解决方法

web开发中如果遇到php查询mysql返回大量数据导致内存溢出.或者内存不够用的情况那就需要看下MySQL C API的关联,那么究竟是什么导致php查询mysql返回大量数据时内存不够用情况? 答案是: mysql_query 和 mysql_unbuffered_query 两个函数 首先来分析一个典型的实例:在执行下面的代码的时候就会导致php请求mysql返回结果太多(10W以上)导致PHP内存不够用. while ($row = mysql_fetch_assoc($result))

Tomcat内存溢出解决办法

使用Java程序从数据库中查询大量的数据时出现异常:java.lang.OutOfMemoryError: Java heap space在JVM中如果98%的时间是用于GC且可用的 Heap size 不足2%的时候将抛出此异常信息.JVM堆的设置是指java程序运行过程中JVM可以调配使用的内存空间的设置.JVM在启动的时候会自动设置Heap size的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4.可以利用JVM提供的-Xmn -Xms -Xmx等

Java 内存溢出(java.lang.OutOfMemoryError)的常见情况和处理方式总结

最近老是遇见服务器内存溢出的问题,故在网上搜了搜,总结了一些java内存溢出的解决方式 java.lang.OutOfMemoryError这个错误我相信大部分开发人员都有遇到过,产生该错误的原因大都出于以下原因:JVM内存过小.程序不严密,产生了过多的垃圾. 导致OutOfMemoryError异常的常见原因有以下几种: 内存中加载的数据量过于庞大,如一次从数据库取出过多数据: 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收: 代码中存在死循环或循环产生过多重复的对象实体: 使用的

内存溢出与内存泄漏

内存溢出 产生原因 Android 的虚拟机是基于寄存器的Delvik,它的最大堆内存是16M,有的机器是24M,因此所能用的内存空间是有限的,如果我们的内存占用超过一定水平就会出现OOM异常 对象内存过大 ---------保存了多个好用内存的过大的对象(比如Bitmap,XML文件),造成内存超出限制 图片过大导致OOM 等比例压缩图片 对图片采用软引用,即使回收 界面切换OOM 1.查看页面布局中有没有大的图片比如背景图之类的 2.直接把XML配置文件加载成View放到容器中 3.页面切换

Android面试题之内存溢出和内存泄漏的问题

在面试中,经常有面试官会问"你知道什么是内存溢出?什么是内存泄漏?怎么避免?"通过这篇文章,你可以回答出来了. 内存溢出 (OOM)是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory:比如只申请了一个integer,但给它存了long才能存下的数,那就会出现内存溢出. 内存泄露 (memory leak)是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光. 内存泄漏最终会导致内存溢

内存溢出和内存泄漏的区别、产生原因以及解决方案 转

内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory:比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出. 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光. memory leak会最终会导致out of memory! 内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产

Android性能优化(一)--关于内存溢出

一个善于自嘲的人,不是因为内心强大,而只是想巧妙地让别人闭嘴.(心情标签) 关于内存溢出的问题 关于栈内存溢出的问题,想必大家或多或少都会遇到过,不像好久不见,却如胶似漆,不经意间都会碰到<( ̄︶ ̄)>.我同桌曾经还对我那么一句煽情却又无比夸张的话,"我敲代码一天不遇到内存溢出,我就难受",他也是够了-.. 今天就以我目前所了解到各种关于内存溢出产生的原因和解决方法,分别与大家分享分享,内容不当或知识点理解片面错误,还望指出. 1.关于优化的了解 Android堆内存也可自

[转]Java内存溢出详解及解决方案

原文地址:http://blog.csdn.net/xianmiao2009/article/details/49254391 内存溢出与数据库锁表的问题,可以说是开发人员的噩梦,一般的程序异常,总是可以知道在什么时候或是在什么操作步骤上出现了异常,而且根据堆栈信息也很容易定位到程序中是某处出现了问题.内存溢出与锁表则不然,一般现象是操作一般时间后系统越来越慢,直到死机,但并不能明确是在什么操作上出现的,发生的时间点也没有规律,查看日志或查看数据库也不能定位出问题的代码. 更严重的是内存溢出与数

JAVA关于POI导出Excel内存溢出的解决方案

JAVA关于POI导出Excel内存溢出的解决方案 在我们使用JAVA开发过程中,经常要导出查询获得的数据,这些数据一般情况下都是以Excel存储的,因此我们在导出数据的时候要使用JAVA的POI库,其主要是对各种windows平台的数据格式进行操作,在这里,我们是对Excel操作. 生成Excel的过程原理是这样的,首先,我们对数据库进行查询,获取相应的结果集,一般是list集合,然后生成Workbook对象,根据生成的Workbook对象获取sheet对象,根据此sheet对象获取Row对象