欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  数据库

SQLSERVER中的假脱机

程序员文章站 2024-01-24 08:03:10
...

SQLSERVER中的假脱机 我发现网上对于假脱机的解释都非常零散,究竟假脱机是什么? 这几天在家里研究了一下,收集了很多网上的资料 假脱机是中文的翻译,而英文的名字叫做 spool 在徐老师写的《SQLSERVER企业级平台管理实践》里提到了一下假脱机 在SQLSERVER

SQLSERVER中的假脱机

我发现网上对于假脱机的解释都非常零散,究竟假脱机是什么?

这几天在家里研究了一下,收集了很多网上的资料

假脱机是中文的翻译,而英文的名字叫做 spool

在徐老师写的《SQLSERVER企业级平台管理实践》里提到了一下假脱机

在SQLSERVER I/O问题的那一节

在性能监视器里,有一个计数器“worktables/sec” :

每秒创建的工作表数。例如,工作表可用于存储查询假脱机(query spool),LOB变量,XML变量,表变量,游标的临时结果

在《剖析SQLServer执行计划》里也提到了假脱机

(13) 有时查询优化器需要在tempdb数据库中建立临时工作表。如果是这样的话

就意味着图形执行计划中有标识成Index Spool, Row Count Spool或者Table Spool的图标。

任何时候,使用到工作表一般都会防碍到性能,因为需要额外的I/O开销来维护这个工作表。

之前本人也写过一篇文章:对于索引假脱机的一点理解

写这篇文章的时候当时还是对假脱机一知半解

假脱机在MSDN中的执行计划中的逻辑运算符和物理运算符中提到了几个假脱机相关的运算符(详见本文最后面)

Eager Spool

Lazy Spool

Index Spool (有时候也叫 Nonclustered Index Spool)

Row Count Spool

Spool

Table Spool

Window Spool

Spool, Table Spool, Index Spool, Window Spool 和 Row Count Spool是物理运算符

Eager Spool 和 Lazy Spool是逻辑运算符

这些运算符描述了假脱机是如何工作的,在这里你需要非常清楚逻辑运算符和物理运算符的区别

MSDN中的解释:

逻辑运算符:逻辑运算符描述了用于处理语句的关系代数操作。 换言之,逻辑运算符从概念上描述了需要执行哪些操作。

物理运算符:物理运算符实施由逻辑运算符描述的操作。 每个物理运算符都是一个执行某项操作的对象或例程。

例如,某些物理运算符可访问表、索引或视图中的列或行。 其他物理运算符执行其他操作,如计算、聚合、数据完整性检查或联接。

物理运算符具有与其关联的开销。

SQLSERVER中的假脱机

注意:窗口假脱机是没有Eager Spool和Lazy Spool之分的,因为他既是逻辑运算符也是物理运算符!!

简单来讲SQLSERVER做某项操作由物理运算符来做,而具体怎样做就由逻辑运算符来决定

打个比方:小明在佛山,想去广州,小明可以选择开汽车去广州,踩自行车去广州,骑摩托车去广州(相当于做某项操作)

小明可以根据当时的路况:

(1)踩自行车:如果道路比较拥堵,踩自行车不用怕,最多的车也能过,他可以选择使劲的踩(Eager Spool)或者慢慢踩(Lazy Spool)

(2)开汽车:如果道路比较畅通,他可以选择开快一点(Eager Spool)或者开慢一点(Lazy Spool)

(3)骑摩托车:如果道路比较拥堵,他可以选择抄小路,然后开快一点(Eager Spool)或者开慢一点(Lazy Spool)

不知道这个比喻大家明白没有,不过本人也找不到更好的比喻~

在图形执行计划中,你会发现Table Spool 有时候会带有 Eager Spool ,有时候有会带有 Lazy Spool

因为Table Spool是物理运算符,Eager Spool和Eager Spool 是逻辑运算符


Table Spool(表假脱机)

SQL脚本如下:

表假脱机 Eager Spool

SQLSERVER中的假脱机SQLSERVER中的假脱机

 1 ----表假脱机 Eager Spool
 2 USE [Spool]
 3 GO
 4 CREATE TABLE Sales (EmpId INT, Yr INT, Sales MONEY)
 5 INSERT Sales VALUES(1, 2005, 12000)
 6 INSERT Sales VALUES(1, 2006, 18000)
 7 INSERT Sales VALUES(1, 2007, 25000)
 8 INSERT Sales VALUES(2, 2005, 15000)
 9 INSERT Sales VALUES(2, 2006, 6000)
10 INSERT Sales VALUES(3, 2006, 20000)
11 INSERT Sales VALUES(3, 2007, 24000)
12 
13 SELECT * FROM [dbo].[Sales]
14 
15 
16 SELECT EmpId, Yr, SUM(Sales) AS Sales
17 FROM Sales
18 GROUP BY EmpId, Yr WITH CUBE
View Code

SQLSERVER中的假脱机

例子出处:http://www.sqlskills.com/blogs/conor/grouping-sets-rollups-and-cubes-oh-my/

In this case, it writes the data to a temporary spool, sorts the output of that

and then re-reads that spool in the second branch.

表假脱机 Lazy Spool

SQLSERVER中的假脱机SQLSERVER中的假脱机

1 --表假脱机 Lazy Spool
2 USE [AdventureWorks]
3 GO
4 SELECT *,COUNT(*) OVER()
5 from production.[Product] AS p
6 JOIN production.[ProductSubcategory] AS s
7 ON s.[ProductCategoryID]=p.[ProductSubcategoryID]
View Code

SQLSERVER中的假脱机

例子出处:http://sqlblog.com/blogs/rob_farley/archive/2013/06/11/spooling-in-sql-execution-plans.aspx


Row Count Spool(行计数假脱机)

SQL脚本如下:

SQLSERVER中的假脱机SQLSERVER中的假脱机

 1 --行计数假脱机
 2 USE [Spool]
 3 GO
 4 --建表
 5 CREATE TABLE tb1(ID int) 
 6 GO
 7 CREATE TABLE tb2(ID int) 
 8 GO
 9 
10 --插入测试数据
11 DECLARE @i INT 
12 SET @i= 500 
13 WHILE @i > 0 
14 begin 
15 INSERT INTO dbo.tb1 
16 VALUES ( @i 
17 ) 
18 SET @i = @i -1 
19 end 
20 GO
21 
22 DECLARE @i INT 
23 SET @i= 500 
24 WHILE @i > 0 
25 begin 
26 INSERT INTO dbo.tb2
27 VALUES ( @i 
28 ) 
29 SET @i = @i -1 
30 end 
31 
32 
33 --行计数假脱机
34 SELECT * FROM tb1 WHERE id NOT IN(SELECT id FROM tb2)
View Code

SQLSERVER中的假脱机

例子出处:http://niutuku.com/tech/MsSql/238716.shtml


Index Spool (索引假脱机)

Lazy Spool

SQL脚本如下:

SQLSERVER中的假脱机SQLSERVER中的假脱机

 1 --索引假脱机(Index Spool)
 2 USE [Spool]
 3 GO
 4 --建表
 5 create  table   tb(aa   int,bb   char(1)) 
 6 GO  
 7 
 8 --插入测试数据
 9 insert   tb   values(1,'A')   
10 insert   tb   values(1,'B')   
11 insert   tb   values(1,'C')   
12 insert   tb   values(1,'D')   
13 
14 insert   tb   values(2,'E')   
15 insert   tb   values(2,'F')   
16 insert   tb   values(2,'G')   
17 insert   tb   values(2,'H')   
18 
19 insert   tb   values(3,'I')   
20 insert   tb   values(3,'J')   
21 insert   tb   values(3,'K')   
22 insert   tb   values(3,'L')
23   
24 --查询数据
25 SELECT  *
26 FROM    tb a
27 WHERE   bb = ( SELECT TOP 1
28                         bb
29                FROM     tb
30                WHERE    aa = a.aa
31                ORDER BY NEWID()
32              )
View Code

SQLSERVER中的假脱机

例子出处:http://www.cnblogs.com/lyhabc/archive/2013/04/19/3029840.html


Window Spool (窗口假脱机)

Window Spool 这个执行计划和OVER() 开窗函数息息相关,因为只有OVER()函数才会使用到Window Spool 这个执行计划

http://msdn.microsoft.com/zh-cn/library/ms189461.aspx

大家可以看一下MSDN中对OVER()开窗函数里ROWS选项RANGE选项的解释

ROWS | RANGE
通过指定分区中的起点和终点,进一步限制分区中的行数。 这是通过按照逻辑关联或物理关联对当前行指定某一范围的行实现的。

物理关联通过使用 ROWS 子句实现。

ROWS 子句通过指定当前行之前或之后的固定数目的行,限制分区中的行数。

此外,RANGE 子句通过指定针对当前行中的值的某一范围的值,从逻辑上限制分区中的行数。

基于 ORDER BY 子句中的顺序对之前和之后的行进行定义。

窗口框架“RANGE … CURRENT ROW …”包括在 ORDER BY 表达式中与当前行具有相同值的所有行。

例如,ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 意味着该函数对其操作的行的窗口在大小上是 3 行,以当前行之前(包括当前行)的 2 行开头。

SQL脚本如下:

SQLSERVER中的假脱机SQLSERVER中的假脱机

 1 use master
 2 GO
 3 
 4 --range
 5 select count(*) over (order by id RANGE between current row and unbounded following)
 6 from   sysobjects
 7 order by id
 8 
 9 --rows
10 select count(*) over (order by type ROWS current row )
11 from   sysobjects
12 order by id
View Code

SQLSERVER中的假脱机

例子出处:http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=182542


对上面这些运算符的解释:

假脱机运算符会取出表中的一部分的数据集,将他们存放在tempdb数据库里的临时结构里

这个临时结构一般就是堆表或者非聚集索引,但是有一个物理运算符例外,临时结构是不存放数据的,

他只存放假脱机里保存的数据的行数,而这个物理运算符就是Row Count spool

Index Spool:索引假脱机只有非聚集索引假脱机,没有聚集索引假脱机,结合我以前写的两篇文章,解释一下原因

SQLSERVER聚集索引与非聚集索引的再次研究(上)

SQLSERVER聚集索引与非聚集索引的再次研究(下)

SQLSERVER当遇到复杂的查询的时候,需要把部分结果集放到tempdb数据库里的非聚集索引页里(说白了就是在tempdb数据库里建立

表的非聚集索引)以加快查找的速度的时候就会用到索引假脱机

例如上面的例子,SQL语句用到了子查询(tb表),SQLSERVER需要把子查询里的结果集(tb表)进行排序然后将结果集放进去

非聚集索引里(对tb表建立非聚集索引),

然后用非聚集索引里的数据和主表(tb a)里的数据进行联接,并输出结果

SQLSERVER中的假脱机

为什麽不用聚集索引?

SQLSERVER聚集索引与非聚集索引的再次研究(上/下)里说到,非聚集索引和堆表是没有连接在一起的,非聚集索引页面只有指针

指向堆表的数据页,而聚集索引的叶子节点就是数据页,索引页和数据页连接在一起,如果建立聚集索引,就需要将表(tb表)中的数据

放入到tempdb数据库里,这样开销就会很大

或者用下面两张图来描述可能会清楚一点,关键还是要读懂 SQLSERVER聚集索引与非聚集索引的再次研究(上/下)

SQLSERVER中的假脱机

SQLSERVER中的假脱机

Table Spool:把表中的数据放进tempdb数据库里

SQLSERVER中的假脱机

为什麽第一个查询会用到Table Spool?因为CUBE这个数据汇总关键字会将表中的数据进行汇总,汇总的过程比较复杂

把表中的数据放进去tempdb数据库里的工作表(worktable、临时表、堆表)里进行复杂的汇总计算是比较好的

他避免了阻塞,以防止长期锁住表中的数据

关于CUBE关键字可以看一下我这篇文章:SQLSERVER中的ALL、PERCENT、CUBE关键字、ROLLUP关键字和GROUPING函数

Row Count Spool:存放中间结果/表的数据的行数,上面的例子里用于计算表中的数据行数并保存在tempdb数据库的

Row Count Spool里,为后面两表联接选用执行计划提供选择依据

SQLSERVER中的假脱机

Eager Spool逻辑运算符:一次性将所有数据放入到Spool里

Lazy Spool逻辑运算符:逐次逐次地将数据放入Spool里

在上面的例子里

Tabel Spool Eager Spool

SQLSERVER中的假脱机

SQLSERVER使用Eager Spool一次性将Sales 表中的数据存放到tempdb数据库的工作表里面,方便快速统计

Row Count Spool

SQLSERVER中的假脱机

SQLSERVER使用计数器每次读取到一行就加1,这样一次一次地统计表中的行数(这里只是比喻,SQLSERVER内部可能并不是这样统计!)

Window Spools:根据MSDN中的定义,OVER 子句定义查询结果集内的窗口或用户指定的行集。 然后,开窗函数将计算窗口中每一行的值

SQLSERVER将窗口中的结果集放入Spool里,以加快后续操作的速度

对于单独一个窗口来讲:单独一个窗口属于Eager Spool(一次性将结果集放进去窗口里)

对于表中的窗口来讲:属于Lazy Spool ,因为每个窗口把数据存放进去窗口里的速度/顺序不是一致的,逐次逐次地将数据存放进去每个窗口

SQLSERVER中的假脱机


为什麽需要假脱机?

主要有两个原因:

1:数据需要再次被调用

2:使假脱机数据与源数据保持隔离

第二个原因很容易理解,就像第一个例子中的Tabel Spool那样,需要把表数据放进Tabel Spool里,以方便进行数据汇总,

而不影响原表数据

第一个原因可以再举一个例子

公用表表达式(CTE)

 1 USE [AdventureWorks]
 2 GO
 3 WITH managers AS(
 4 SELECT [EmployeeID],[ManagerID]
 5 from [HumanResources].[Employee]
 6 WHERE [ManagerID] IS NULL
 7 UNION ALL
 8 SELECT e.[EmployeeID],e.[ManagerID]
 9 from [managers] m
10 JOIN [HumanResources].[Employee] e
11 ON e.[ManagerID]=m.[EmployeeID]
12 )
13 
14 SELECT * FROM [managers]

SQLSERVER中的假脱机

索引假脱机运算符负责把数据一条一条地塞进去tempdb的非聚集索引里,并且是Lazy的,为什麽是Lazy的?

因为刚开始的时候只有一行记录,后来慢慢一条一条数据地从最右边的表假脱机里获取数据

SQLSERVER中的假脱机

我们还是先分析一下整个执行计划以方便理解,我们可以将整个执行计划拆解为三部分

第一部分 执行计划的右上角

1 SELECT [EmployeeID],[ManagerID]
2 from [HumanResources].[Employee]
3 WHERE [ManagerID] IS NULL

SQLSERVER中的假脱机

这部分的执行计划只查找到一条记录

SQLSERVER中的假脱机

他把这条记录放入索引假脱机里

SQLSERVER中的假脱机

第二部分 UNION ALL

SQLSERVER中的假脱机

SQLSERVER中的假脱机

将第一部分的结果和第三部分的结果合并在一起

第三部分 执行计划的右下角

1 SELECT e.[EmployeeID],e.[ManagerID]
2 from [managers] m
3 JOIN [HumanResources].[Employee] e
4 ON e.[ManagerID]=m.[EmployeeID]

SQLSERVER中的假脱机

最右边的表假脱机运算符负责把表数据装载入表假脱机里,这个装载过程也是逐条数据装载的

那么,执行计划里的表假脱机和索引假脱机主要有什么用???

表假脱机主要用作公用表表达式里的递归调用

 1 WITH managers AS(
 2 SELECT [EmployeeID],[ManagerID]
 3 from [HumanResources].[Employee]
 4 WHERE [ManagerID] IS NULL
 5 UNION ALL
 6 SELECT e.[EmployeeID],e.[ManagerID]
 7 from [managers] m
 8 JOIN [HumanResources].[Employee] e
 9 ON e.[ManagerID]=m.[EmployeeID]
10 )

SELECT e.[EmployeeID],e.[ManagerID]
from [managers] m
JOIN [HumanResources].[Employee] e
ON e.[ManagerID]=m.[EmployeeID]

上面的代码是每次递归的时候都需要调用到的,所以SQLSERVER干脆把表数据放到假脱机里的,不用每次都去查找记录了

SQLSERVER中的假脱机

而索引假脱机是方便外部代码调用公用表表达式的时候不用每次都去计算公用表表达式的结果,直接把公用表表达式的结果

放进去索引假脱机,当SELECT * FROM managers的时候,直接到索引假脱机里取数据就可以了

1 SELECT * FROM [managers]

SQLSERVER中的假脱机

断定运算符在这里的作用是判断是否超过系统循环次数造成死循环,如果我们加上OPTION (MAXRECURSION 0)

断定运算符就会消失

SQLSERVER中的假脱机

 1 USE [AdventureWorks]
 2 GO
 3 WITH managers AS(
 4 SELECT [EmployeeID],[ManagerID]
 5 from [HumanResources].[Employee]
 6 WHERE [ManagerID] IS NULL
 7 UNION ALL
 8 SELECT e.[EmployeeID],e.[ManagerID]
 9 from [managers] m
10 JOIN [HumanResources].[Employee] e
11 ON e.[ManagerID]=m.[EmployeeID]
12 )
13 
14 SELECT * FROM [managers] OPTION (MAXRECURSION 0)

SQLSERVER中的假脱机


万圣节问题

网上有两篇文章介绍了这个问题

园子里的这篇文章介绍非常不深入,看了之后还是不明白

http://www.cnblogs.com/xwdreamer/archive/2012/05/28/2522404.html

simple-talk网站的文章就介绍得非常清晰

https://www.simple-talk.com/sql/learn-sql-server/operator-of-the-week---spools,-eager-spool/

在介绍之前先来做一个小实验

下面SQL脚本建立一个非聚集索引表,并且非聚集索引的第一个字段是salary 并且按salary升序排序!!!

SQLSERVER中的假脱机SQLSERVER中的假脱机

 1 USE [Spool]
 2 GO
 3 
 4 CREATE TABLE nct(id INT IDENTITY(1,1),NAME VARCHAR(30), salary INT);
 5 GO
 6 --建立非聚集索引  切记:非聚集索引的第一个字段是salary 并且按salary升序排序!!!
 7 CREATE  INDEX ix_nct ON nct(salary ASC,[ID],[NAME]) 
 8 GO
 9  
10 --插入数据
11 INSERT INTO [dbo].[nct] ( [NAME],[salary] )
12 SELECT '小明', 1 UNION ALL
13 SELECT '小华', 2 UNION ALL
14 SELECT '小芳', 3
15 GO
16 
17 SELECT * FROM [dbo].[nct]
View Code

SQLSERVER中的假脱机

我们看一下非聚集索引页

SQLSERVER中的假脱机SQLSERVER中的假脱机

 1 CREATE TABLE DBCCResult (
 2 PageFID NVARCHAR(200),
 3 PagePID NVARCHAR(200),
 4 IAMFID NVARCHAR(200),
 5 IAMPID NVARCHAR(200),
 6 ObjectID NVARCHAR(200),
 7 IndexID NVARCHAR(200),
 8 PartitionNumber NVARCHAR(200),
 9 PartitionID NVARCHAR(200),
10 iam_chain_type NVARCHAR(200),
11 PageType NVARCHAR(200),
12 IndexLevel NVARCHAR(200),
13 NextPageFID NVARCHAR(200),
14 NextPagePID NVARCHAR(200),
15 PrevPageFID NVARCHAR(200),
16 PrevPagePID NVARCHAR(200)
17 )
18 
19 --TRUNCATE TABLE [dbo].[DBCCResult]
20 
21 INSERT INTO DBCCResult EXEC ('DBCC IND(Spool,nct,-1) ')
22 
23 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 
24 
25 DBCC TRACEON(3604,-1)
26 GO
27 DBCC PAGE(Spool,1,47,3) 
28 GO
View Code

SQLSERVER中的假脱机

非聚集索引按照Salary字段升序排序

我们用SQL语句update一下小华的Salary

1 UPDATE nct SET Salary = 4
2 WHERE [NAME]='小华'

SQLSERVER中的假脱机

这里是按照非聚集索引的Range Scan读取出结果的:SQLSERVER中的ALLOCATION SCAN和RANGE SCAN

再看一下非聚集索引页面

SQLSERVER中的假脱机

我们看一下update前和update后非聚集索引页面的变化

SQLSERVER中的假脱机

可以看到,update之后非聚集索引马上根据非聚集索引键(Salary字段)重新进行升序排序

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

使用下面SQL脚本建立测试环境

SQLSERVER中的假脱机SQLSERVER中的假脱机

 1 USE [Spool]
 2 GO
 3 
 4 
 5 --建表
 6 CREATE TABLE Halloween
 7 (
 8   ID INT IDENTITY(1, 1)
 9          PRIMARY KEY ,
10   Name VARCHAR(30) ,
11   Salary NUMERIC(18, 2),
12   Remark NVARCHAR(3000)
13 )
14 GO
15  
16 --插入数据
17 INSERT INTO [dbo].[Halloween] ( [Name], [Salary], [Remark] )
18 SELECT '小明',1,replicate('a', 3000) UNION ALL
19 SELECT '小方',2,replicate('a', 3000) 
20 
21 
22 
23 
24 --建立非聚集索引
25 CREATE NONCLUSTERED INDEX ix_Halloween ON Halloween(Salary ASC)
26 GO
27 
28 --查询
29 SELECT * FROM Halloween
30 GO
View Code

SQLSERVER中的假脱机

我们用下面SQL语句看一下聚集索引页面和非聚集索引页面

SQLSERVER中的假脱机SQLSERVER中的假脱机

 1 CREATE TABLE DBCCResult (
 2 PageFID NVARCHAR(200),
 3 PagePID NVARCHAR(200),
 4 IAMFID NVARCHAR(200),
 5 IAMPID NVARCHAR(200),
 6 ObjectID NVARCHAR(200),
 7 IndexID NVARCHAR(200),
 8 PartitionNumber NVARCHAR(200),
 9 PartitionID NVARCHAR(200),
10 iam_chain_type NVARCHAR(200),
11 PageType NVARCHAR(200),
12 IndexLevel NVARCHAR(200),
13 NextPageFID NVARCHAR(200),
14 NextPagePID NVARCHAR(200),
15 PrevPageFID NVARCHAR(200),
16 PrevPagePID NVARCHAR(200)
17 )
18 
19 --TRUNCATE TABLE [dbo].[DBCCResult]
20 INSERT INTO DBCCResult EXEC ('DBCC IND(spool,Halloween,-1) ')
21 
22 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 
23 
24 
25 DBCC TRACEON(3604,-1)
26 GO
27 DBCC PAGE(spool,1,184,3) 
28 GO
29 DBCC PAGE(spool,1,93,3) 
30 GO
View Code

聚集索引页面

SQLSERVER中的假脱机

非聚集索引页面

SQLSERVER中的假脱机

我们update一下Salary等于1的那位员工的工资

1 UPDATE Halloween SET Salary = 2.5
2 FROM Halloween 
3 WHERE Salary =1

再看一下聚集索引页面和非聚集索引页面

聚集索引页面

SQLSERVER中的假脱机

非聚集索引页面

SQLSERVER中的假脱机

非聚集索引马上按照非聚集索引键(Salary字段)进行重新排序

这里似乎没有什么问题,我们drop掉Halloween表,并重新建立测试环境

SQLSERVER中的假脱机SQLSERVER中的假脱机

 1 USE [Spool]
 2 GO
 3 
 4 
 5 
 6 --建表
 7 CREATE TABLE Halloween
 8 (
 9   ID INT IDENTITY(1, 1)
10          PRIMARY KEY ,
11   Name VARCHAR(30) ,
12   Salary NUMERIC(18, 2),
13   Remark NVARCHAR(3000)
14 )
15 GO
16  
17 --插入数据
18 INSERT INTO [dbo].[Halloween] ( [Name], [Salary], [Remark] )
19 SELECT '小明',1,replicate('a', 3000) UNION ALL
20 SELECT '小方',2,replicate('a', 3000) 
21 
22 
23 
24 
25 --建立非聚集索引
26 CREATE NONCLUSTERED INDEX ix_Halloween ON Halloween(Salary ASC)
27 GO
28 
29 --查询
30 SELECT * FROM Halloween
31 GO
32 
33 
34 
35 
36 
37 CREATE TABLE DBCCResult (
38 PageFID NVARCHAR(200),
39 PagePID NVARCHAR(200),
40 IAMFID NVARCHAR(200),
41 IAMPID NVARCHAR(200),
42 ObjectID NVARCHAR(200),
43 IndexID NVARCHAR(200),
44 PartitionNumber NVARCHAR(200),
45 PartitionID NVARCHAR(200),
46 iam_chain_type NVARCHAR(200),
47 PageType NVARCHAR(200),
48 IndexLevel NVARCHAR(200),
49 NextPageFID NVARCHAR(200),
50 NextPagePID NVARCHAR(200),
51 PrevPageFID NVARCHAR(200),
52 PrevPagePID NVARCHAR(200)
53 )
54 
55 --TRUNCATE TABLE [dbo].[DBCCResult]
56 INSERT INTO DBCCResult EXEC ('DBCC IND(spool,Halloween,-1) ')
57 
58 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 
59 
60 
61 DBCC TRACEON(3604,-1)
62 GO
63 DBCC PAGE(spool,1,184,3) 
64 GO
65 DBCC PAGE(spool,1,93,3) 
66 GO
View Code

这次我们使用下面update语句,记住一定要加WITH(INDEX=ix_Halloween)

1 USE [Spool]
2 GO
3 UPDATE Halloween SET Salary = [Salary]*2.5
4 FROM Halloween WITH(INDEX=ix_Halloween)
5 WHERE Salary 7

如果我们加了WITH(INDEX=ix_Halloween),SQLSERVER就会走非聚集索引查找

SQLSERVER中的假脱机

如果我们不加WITH(INDEX=ix_Halloween),SQLSERVER就会走聚集索引扫描

SQLSERVER中的假脱机

这里不讨论加不加WITH(INDEX=ix_Halloween)的问题

关键我们加WITH(INDEX=ix_Halloween)就是为了让SQLSERVER走非聚集索引

update了之后正常的结果应该是这样的

SQLSERVER中的假脱机

为什麽会这样?

还记得刚才我们说到了非聚集索引更新了之后马上进行排序吗?

用下面的图来表示应该会比较清楚

SQLSERVER中的假脱机

SQLSERVER中的假脱机

SQLSERVER中的假脱机

SQLSERVER使用Table Spool来解决万圣节问题

SQLSERVER中的假脱机

SQLSERVER中的假脱机

先将非聚集索引的数据放进去Table Spool(工作表)里,然后逐行逐行扫描工作表,这样就不会遇到非聚集索引更新后马上进行排序的问题了

使用Table Spool后就能够得到正确结果

SQLSERVER中的假脱机

为什麽不用Index Spool而用Table Spool?

之前我们说过Index Spool在tempdb数据库里建立临时的非聚集索引,把非聚集索引里的数据

放进去非聚集索引里,那不是会继续遇到万圣节问题???

下面这个SQL语句也是使用了Table Spool来避免万圣节问题

1 USE [AdventureWorks]
2 GO
3 UPDATE  s
4 SET     [Name] = 'Z' + [Name]
5 FROM    Production.ProductSubcategory AS s WITH ( INDEX ( [AK_ProductSubcategory_Name] ) )
6 WHERE   [Name] >= 'N'

SQLSERVER中的假脱机

SQLSERVER中的假脱机

总结一下万圣节问题

update数据的时候,如果update的是非聚集索引的第一个字段(即非聚集索引键)的时候并且走的是非聚集索引扫描/查找

都有可能引起万圣节问题

SQLSERVER的解决方法是把非聚集索引里的数据全部移到Tabel Spool(Eager)里

防止由于更新非聚集索引的非聚集索引键而引起的非聚集索引重新排序,造成数据更新错误的问题


总结

实际上这些假脱机运算符的本质跟临时表和表变量是一样的,都是以空间换时间,以达到性能上的平衡!

文章最后面附上MSDN里的SQLSERVER所有的执行计划(逻辑运算符和物理运算符)

参考文章:

http://www.scarydba.com/2009/09/09/spools-in-execution-plans/

https://www.simple-talk.com/sql/learn-sql-server/operator-of-the-week---spools,-eager-spool/

http://sqlblog.com/blogs/rob_farley/archive/2013/06/11/spooling-in-sql-execution-plans.aspx

如有不对的地方,欢迎大家拍砖o(∩_∩)o


SQLSERVER中的假脱机

SQLSERVER中的假脱机

SQLSERVER中的假脱机

SQLSERVER中的假脱机

SQLSERVER中的假脱机

SQLSERVER中的假脱机

SQLSERVER中的假脱机


Showplan 逻辑运算符和物理运算符参考

操作说明


本节介绍了各个逻辑运算符和物理运算符。

图形执行计划图标

Showplan 运算符

说明

Aggregate

Aggregate 运算符计算包含 MIN、MAX、SUM、COUNT 或 AVG 的表达式。 Aggregate 既是一个逻辑运算符,也是一个物理运算符。

SQLSERVER中的假脱机

Arithmetic Expression

Arithmetic Expression 运算符根据行中的现有值计算新值。 SQL Server 2012 中不使用 Arithmetic Expression

SQLSERVER中的假脱机

Assert

Assert 运算符用于验证条件。 例如,验证引用完整性或确保标量子查询返回一行。 对于每个输入行,Assert 运算符都要计算执行计划的Argument 列中的表达式。 如果此表达式的值为 NULL,则通过 Assert 运算符传递该行,并且查询执行将继续。 如果此表达式的值非 Null,则将产生相应的错误。 Assert 运算符是一个物理运算符。

SQLSERVER中的假脱机

Assign

Assign 运算符将表达式的值或常量分配给变量。 Assign 是一个语言元素。

Asnyc Concat

Asnyc Concat 运算符仅用于远程查询(分布式查询)。 它有 n 个子节点和一个父节点。 通常,某些子节点是参与分布式查询的远程计算机。 Asnyc Concat 同时向所有子节点发出 open() 调用,然后将位图应用于每个子节点。 对于为 1 的每个位,Async Concat 按需向父节点发送输出行。

SQLSERVER中的假脱机

Bitmap

SQL Server 使用 Bitmap 运算符来实现并行查询计划中的位图筛选。 在将行传递给另一个运算符(如 SQLSERVER中的假脱机

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。

相关文章

相关视频


网友评论

文明上网理性发言,请遵守 新闻评论服务协议

我要评论
  • SQLSERVER中的假脱机
  • 专题推荐

    作者信息
    SQLSERVER中的假脱机

    认证0级讲师

    推荐视频教程
  • SQLSERVER中的假脱机javascript初级视频教程
  • SQLSERVER中的假脱机jquery 基础视频教程
  • 视频教程分类

    ");}}}}topobj_second.html(topsecond);topobj_minutes.html(topminutes);topobj_hours.html(tophours);topobj_day.html(topday);},1000);}$('.topimages .layui-icon-close').click(function(){$.cookie('phpcndatatopadshows',1,{expires:7});$('.topimages').hide();});