聚集索引更新后会不会马上重新排序
今天在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
我们用下面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
聚集索引页面
1 DBCC TRACEON(3604,-1) 2 GO 3 DBCC PAGE(spool,1,174,3) 4 GO
我们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
数据页面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
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)
还是那几个页面,没有变化
现在看一下109这个页面
1 DBCC TRACEON(3604,-1) 2 GO 3 DBCC PAGE(spool,1,109,3) 4 GO
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
更新时候的执行计划
聚集索引表脚本
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
更新时候的执行计划
非聚集索引表脚本
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
更新时候的执行计划
总结
聚集索引也会引起万圣节问题
万圣节问题:重新排序导致的更新记录错误
聚集索引更新了之后也会重新排序,所以你看到上面聚集索引表的结果
中间也会有一个表假脱机的运算符
而非聚集索引的执行计划为什麽没有呢?
因为非聚集索引那个扫描的是堆里的数据页面,而不是扫描非聚集索引页面,
只有扫描非聚集索引页面才会出现万圣节问题,更新了非聚集索引之后马上进行排序
而堆里的数据页面无论怎麽更新都不会重新排序的
这里归纳一下重新排序的情况
重新排序:扫描聚集索引(聚集索引表)、扫描非聚集索引(非聚集索引表)
不重新排序:扫描堆里的数据页面(非聚集索引表)、堆表
非聚集索引比较特殊,因为索引页面和数据页面是分开的,所以要分两种情况
如有不对的地方,欢迎大家拍砖o(∩_∩)o