zoukankan      html  css  js  c++  java
  • 聚集索引更新后会不会马上重新排序

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

    今天在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

  • 相关阅读:
    如何设计一个秒杀系统
    Leetcode题目437:路径总和III(递归-简单)
    Leetcode题目461:汉明距离(位运算-简单)
    Leetcode题目617:合并二叉树(递归-简单)
    分布式锁
    分布式搜索引擎
    数据库
    Java知识体系思维导图
    wav文件头详解,看懂wav文件
    推荐一个最近在学习的AI算法工程师手册,侵删
  • 原文地址:https://www.cnblogs.com/lyhabc/p/3401373.html
Copyright © 2011-2022 走看看