查询内存溢出

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
简介:

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

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
复制代码



本文转自Woodytu博客园博客,原文链接:http://www.cnblogs.com/woodytu/p/4582627.html,如需转载请自行联系原作者

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
相关文章
|
11月前
|
Oracle 关系型数据库 Linux
解决在linux服务器上部署定时自动查找cpu,内存,磁盘使用量,并将查询结果写入数据库的脚本,只能手动运行实现插库操作
问题描述:将脚本名命名为mortior.sh(以下简称mo),手动执行脚本后查询数据库,表中有相应的信息,放入自动执行队列中,脚本被执行,但是查询数据库,并没有新增数据。
76 0
|
7天前
|
存储 关系型数据库 MySQL
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
49 5
|
2月前
|
存储 Java API
【Azure Developer】通过Azure提供的Azue Java JDK 查询虚拟机的CPU使用率和内存使用率
【Azure Developer】通过Azure提供的Azue Java JDK 查询虚拟机的CPU使用率和内存使用率
|
5月前
|
存储 Web App开发 运维
|
5月前
|
存储 缓存 监控
Linux 系统 内存通用指标以及查询方式
Linux 系统 内存通用指标以及查询方式
52 0
|
5月前
|
SQL 算法 关系型数据库
大查询会不会把内存打爆
大查询会不会把内存打爆
|
5月前
|
运维 Linux
Linux 查询 OS、CPU、内存、硬盘信息
Linux 查询 OS、CPU、内存、硬盘信息
130 0
|
SQL 存储 缓存
MySQL高级第三篇(共四篇)之应用优化、查询缓存优化、内存管理优化、MySQL锁问题、常用SQL技巧(一)
前面章节,我们介绍了很多数据库的优化措施。但是在实际生产环境中,由于数据库本身的性能局限,就必须要对前台的应用进行一些优化,来降低数据库的访问压力。
16086 7
|
存储 SQL 缓存
MySQL高级第三篇(共四篇)之应用优化、查询缓存优化、内存管理优化、MySQL锁问题、常用SQL技巧(二)
锁是计算机协调多个进程或线程并发访问某一资源的机制(避免争抢)。 在数据库中,除传统的计算资源(如 CPU、RAM、I/O 等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
397 0
|
SQL 存储 缓存
【MySQL高级】应用优化及Mysql中查询缓存优化以及Mysql内存管理及优化
【MySQL高级】应用优化及Mysql中查询缓存优化以及Mysql内存管理及优化
192 0
【MySQL高级】应用优化及Mysql中查询缓存优化以及Mysql内存管理及优化

热门文章

最新文章

下一篇
无影云桌面