聚集索引更新后会不会马上重新排序

聚集索引更新后会不会马上重新排序

今天在QQ群里有人问到下面问题

 

提问人用的是MYSQL,不过这个问题让我想起了SQLSERVER的万圣节问题

万圣节问题就是因为更新了非聚集索引之后,非聚集索引迅速排序导致的更新错误问题

详见:SQLSERVER中的假脱机

当时我只是测试了非聚集索引下面的情况,但是聚集索引下面有没有这种情况呢?

我们修改一下SQLSERVER中的假脱机中的脚本,将建立非聚集索引的那条语句改为建立聚集索引

 

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

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

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

 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 
View Code

聚集索引页面

1 DBCC TRACEON(3604,-1)
2 GO
3 DBCC PAGE(spool,1,174,3) 
4 GO
View Code

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

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

再看一下聚集索引页面

 

可以看到SQLSERVER增加了页面89来保存结果,就是说聚集索引也是在更新之后马上排序的

那么页面89和页面109里面到底有什么内容呢?

1 DBCC TRACEON(3604,-1)
2 GO
3 DBCC PAGE(spool,1,109,3) 
4 GO
View Code

数据页面109

 1 PAGE: (1:109)
 2 
 3         
 4          
 5 UNIQUIFIER = [NULL]                  
 6 
 7 Slot 0 Column 1 Offset 0x4 Length 9
 8 
 9 Salary = 1.00                        
10 
11 Slot 0 Column 2 Offset 0x1c Length 4
12 
13 Name = 小明                          
14 
15 Slot 0 Column 3 Offset 0xd Length 4
16 
17 ID = 1                               
18 
19 Slot 0 Column 4 Offset 0x20 Length 6000
20 
21 Remark = aaaa

数据页面89

1 DBCC TRACEON(3604,-1)
2 GO
3 DBCC PAGE(spool,1,89,3) 
4 GO
View Code
 1 PAGE: (1:89)
 2 
 3 Slot 0 Column 1 Offset 0x4 Length 9
 4 
 5 Salary = 2.50                        
 6 
 7 Slot 0 Column 2 Offset 0x1c Length 4
 8 
 9 Name = 小明                          
10 
11 Slot 0 Column 3 Offset 0xd Length 4
12 
13 ID = 1                               
14 
15 Slot 0 Column 4 Offset 0x20 Length 6000
16 
17 Remark = aaa 

 

大家马上会问:为什麽109这个页面不删除他,还存放着旧的记录,留在那里占用空间???

除非你重组聚集索引或者重建聚集索引,不然的话SQLSERVER不会马上删除页面的

当你又插入一条记录,并且这条记录的Salary小于2,那么SQLSERVER就会把这条记录保存到109这个页面

1 INSERT INTO [dbo].[Halloween] ( [Name], [Salary], [Remark] )
2 SELECT '小新',1,replicate('a', 3000) 

看一下Halloween表的页面分配情况

还是那几个页面,没有变化

现在看一下109这个页面

1 DBCC TRACEON(3604,-1)
2 GO
3 DBCC PAGE(spool,1,109,3) 
4 GO
View Code
 1 PAGE: (1:109)
 2      
 3        
 4 UNIQUIFIER = [NULL]                  
 5 
 6 Slot 0 Column 1 Offset 0x4 Length 9
 7 
 8 Salary = 1.00                        
 9 
10 Slot 0 Column 2 Offset 0x1c Length 4
11 
12 Name = 小新                          
13 
14 Slot 0 Column 3 Offset 0xd Length 4
15 
16 ID = 3                               
17 
18 Slot 0 Column 4 Offset 0x20 Length 6000
19 
20 Remark = aaa

这个问题也在这篇文章介绍过:聚集索引表插入数据和删除数据的方式是怎样的


关于第一张图片的结果

堆表脚本

 1 CREATE TABLE Halloween2
 2 (
 3   ID INT ,
 4   Name VARCHAR(30) ,
 5   Salary NUMERIC(18, 2),
 6   Remark NVARCHAR(3000)
 7 )
 8 GO
 9  
10 --插入数据
11 INSERT INTO [dbo].[Halloween2] ([id] ,[Name], [Salary], [Remark] )
12 SELECT 1, '小明',2,replicate('a', 3000) UNION ALL
13 SELECT 2, '小方',3,replicate('a', 3000) 
14 
15 
16 
17 
18 
19 --查询
20 SELECT * FROM Halloween2
21 GO
22 
23 UPDATE  Halloween2
24 SET     Salary = id ,[id] = salary
25 
26 --查询
27 SELECT * FROM Halloween2
28 GO
View Code

更新时候的执行计划

 聚集索引表脚本

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

更新时候的执行计划

非聚集索引表脚本

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

更新时候的执行计划

 


总结

聚集索引也会引起万圣节问题

万圣节问题:重新排序导致的更新记录错误

聚集索引更新了之后也会重新排序,所以你看到上面聚集索引表的结果

中间也会有一个表假脱机的运算符

而非聚集索引的执行计划为什麽没有呢?

因为非聚集索引那个扫描的是堆里的数据页面,而不是扫描非聚集索引页面,

只有扫描非聚集索引页面才会出现万圣节问题,更新了非聚集索引之后马上进行排序

而堆里的数据页面无论怎麽更新都不会重新排序的

 

这里归纳一下重新排序的情况

重新排序:扫描聚集索引(聚集索引表)、扫描非聚集索引(非聚集索引表

不重新排序:扫描堆里的数据页面(非聚集索引表)、堆表

 

非聚集索引比较特殊,因为索引页面和数据页面是分开的,所以要分两种情况

 

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

posted @ 2013-11-01 18:22  桦仔  阅读(2866)  评论(0编辑  收藏  举报