zoukankan      html  css  js  c++  java
  • 查询内存溢出

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

    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,其实一点也不神秘!

    参考文章:

    https://www.sqlpassion.at/archive/2011/10/19/query-memory-spills/

  • 相关阅读:
    排序
    阿里实习 电面,面试
    外部排序
    error: LNK 2019 无法解析的外部符号
    c++ 变量定义 的初始化规则
    Spring 让 LOB 数据操作变得简单易行
    深入浅出JMS(四)--Spring和ActiveMQ整合的完整实例
    Spring jdbc call oralce procedure or function
    JSON 简单封装
    extjs3 用户管理 页面
  • 原文地址:https://www.cnblogs.com/woodytu/p/4582627.html
Copyright © 2011-2022 走看看