zoukankan      html  css  js  c++  java
  • SQLSERVER聚集索引与非聚集索引的再次研究(上)

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

    上篇主要说聚集索引

    下篇的地址:SQLSERVER聚集索引与非聚集索引的再次研究(下)

    由于本人还是SQLSERVER菜鸟一枚,加上一些实验的逻辑严谨性,
    单写《SQLSERVER聚集索引与非聚集索引的再次研究(上)》就用了12个小时,两篇文章加起来最起码写了20个小时,
    本人非常非常用心的努力完成这两篇文章,希望各位看官给点意见o(∩_∩)o

    为了搞清楚索引内部工作原理和结构,真是千头万绪,这篇文章只是作为参考,里面的观点不一定正确

    有一些问题,msdn里,网上的文章里,博客园里都有提到,但是这些问题的答案是正确的吗?其实有时候我自己都想知道答案

    比如,画聚集索引的图,有一些人用表格来表示,但是他们正确吗?

    以前知道聚集索引 非聚集索引是B树 二叉树结构,又知道执行计划图标很像二叉树很传神,但是还是觉得很抽象

    这篇文章写完以后还是比较抽象但是最起码比以前清晰一些了

    有很多问题不知道为什么,但是MSDN就是这样说的,既然说得这麽模糊不如自己做一下实验,验证一下MSDN的内容吧o(∩_∩)o

    --------------------------------------------华丽的分割线---------------------------------------------

     先来看一下索引的结构,文章里面的一些结构图都是自己画的一些草图,本人自认画得非常烂,希望各位看官谅解o(∩_∩)o

     

     

    ----------------------------------------------华丽的分割线---------------------------------------------------------

    先创建一个表,保存DBCC IND的结果

    复制代码
     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 )
    复制代码

    创建一个聚集索引表

    复制代码
    1 --只有聚集索引
    2 CREATE TABLE Department(
    3     DepartmentID int IDENTITY(1,1) NOT NULL PRIMARY KEY,
    4     Name NVARCHAR(200) NOT NULL,
    5     GroupName NVARCHAR(200) NOT NULL,
    6     Company NVARCHAR(300),
    7     ModifiedDate datetime NOT NULL  DEFAULT (getdate())
    8 )
    复制代码

    插入10W条记录

    1 INSERT INTO Department(name,[Company],groupname) VALUES('销售部','中国你好有限公司XX分公司','销售组')
    2 GO 100000

    将DBCC IND的结果放入DBCCRESULT表

    1 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,Department,-1) ')

    查询Department表中的页面情况

    先说明一下:

    PageType  分页类型: 1:数据页面;2:索引页面;3:Lob_mixed_page;4:Lob_tree_page;10:IAM页面

    IndexID    索引ID:   0 代表堆, 1 代表聚集索引, 2-250 代表非聚集索引 ,大于250就是text或image字段

    红色框部分都是需要关注的

    第一个:IAM页不是只有堆表才有也不只是维护堆表中的数据页的连续,有索引的表都有,所以IAM页不只维护数据页,也维护索引页的连续,在下篇说到非聚集索引的时候

    我会给出MSDN的解释和IAM页在聚集索引表,非聚集索引表中的情况

    第二个:每个数据页的IndexID都是1,不是说数据页变成了索引页,而是说现在数据页已经属于聚集索引的一部分,不在堆里了

    第三个:每个数据页的IndexLevel都是0,就是说数据页在聚集索引的最下层

    第四个:索引页和数据页,前一页和后一页是首尾相连的,但是数据页和索引页不是首尾相连的,也就是说没有一个数据页的[PrevPagePID]指向14464页或3528页

    那么在上面的聚集索引图片中为什麽会说索引页指向数据页呢?叶子节点就是数据页呢?

    数据页的index level是0,那么就是说聚集索引的叶子节点就是数据页

     上面索引页的结构

    现在来看一下索引页里都有什么,运行下面的SQL语句

    复制代码
     1 DBCC TRACEON(3604,-1)
     2 GO
     3 
     4 DBCC PAGE([pratice],1,3527,3)
     5 GO
     6 
     7 
     8 DBCC PAGE([pratice],1,3528,3)
     9 GO
    10 
    11 DBCC PAGE([pratice],1,14464,3)
    12 GO
    复制代码

     您们应该看到ChildPageId,所以上面我的图为什麽会这样画的原因,索引页连接着数据页,而且一个索引页指向多个数据页

     DepartmentID是主键列,从1开始自增,那么从下图可以看出主键列数据是从最左边的索引节点(不是叶子节点)开始排序

    这里有个问题:为什麽根节点只有两行???是不是根节点只作连接作用,所以只有两行 ,不过这个问题我也不清楚

    聚集索引页里主键列DepartmentID上一行与下一行相差120条记录,一个数据页刚好容纳120条记录

    KeyHashValue根据主键列的第一个字段而生成的,就算两个表完全一样,这个hash出来的KeyHashValue都不会一样

    我创建了一个一模一样的表Department2,看到hash出来的值都不一样

    而这个KeyHashValue我们就叫做键,也就是key-value中的key

    ------------------------------------------------------------华丽的分割线------------------------------------------------------

    聚集索引怎麽找记录的???

    这里要分两种情况:(1)聚集索引查找和(2)聚集索引扫描

    (1)聚集索引查找

     放大一下索引页

     SQLSERVER首先把每个数据页的头一条记录里的DepartmentID的值加上一定范围值hash出一个key值,然后放在KeyHashValue列里

    当我要找DepartmentID为110的那条记录里的GroupName和Company的值的时候,首先SQLSERVER根据where DepartmentID=110

    将110加上一个范围值hash出一个值,这个值就是KeyHashValue的值,找到KeyHashValue=(f000ff86397c)的那条记录

    然后到数据页13791里找出DepartmentID为110的那条记录里的GroupName和Company的值

     其实这里的算法应该跟hash join是一样的,但是实际具体怎麽算的?本人就不清楚了,大家可以看一下hash join的原理

    个人感觉在SQLSERVER里 key-value hash桶用途很广泛,执行计划、 hash join、 聚集索引都用到了

    证明:这里我可以证明一下SQLSERVER聚集索引查找记录的流程

    先到索引页里找到键值为KeyHashValue=XXX的那条记录,然后再到数据页里把实际数据读出来

     运行下面的SQL语句,看一下SQLSERVER申请的锁就知道了

     下面实验我在Department2表里做的,表数据和表结构和Department1一模一样

     View Code

     下面这个证明代码在《SQLSERVER企业级平台管理实践》里找的

     View Code

     

    (2)聚集索引扫描

    先drop掉Department2表,然后重新创建Department2表

     View Code

    证明:

     View Code

    上图“以下查询使用了聚集索引查找”,由于本人写SQL代码的时候没有修改上面注释,大家可以不用理会

    为什麽会有一个键锁,那么多的页锁,在徐海蔚老师的《SQLSERVER企业级平台管理实践》的书本里第361页说到

    因为在有聚集索引的表格上,数据是直接存放在索引的最底层(叶子节点),所以要扫描整个表格里的数据,就要把整个聚集索引

    扫描一遍。在这里,聚集索引扫描就相当于一个表扫描。所要用的时间和资源与表扫描没有什么差别

    再看一下聚集索引查找的流程

    SQLSERVER首先把每个数据页的头一条记录里的DepartmentID的值加上一定范围值hash出一个key值,然后放在KeyHashValue列里

    当我要找DepartmentID为110的那条记录里的GroupName和Company的值的时候,首先SQLSERVER根据where DepartmentID=110

    将110加上一个范围值hash出一个值,这个值就是KeyHashValue的值,找到KeyHashValue=(f000ff86397c)的那条记录

    然后到数据页13791里找出DepartmentID为110的那条记录里的GroupName和Company的值

    因为[GroupName]列不是索引列,所以根本找不到KeyHashValue值,所以这里只能使用扫描所有数据页的方法来找出记录,除非找到那条记录

    不然SQLSERVER不会停止扫描数据页,所以才看到上图有那么多的页面上加了页锁,SQLSERVER需要逐个数据页逐个数据页去扫描就像堆表的全表扫描那样。

    那个键锁我估计是当SQLSERVER找到那条记录之后,需要在

    记录的所在页面(即是索引页指向那个记录的数据页的那一行)加上一个键锁,以防止别人删除索引页的那一行记录

    但是聚集索引扫描是不是一定比聚集索引查找要差呢?这个不一定,要看实际情况o(∩_∩)o

    那么非聚集索引扫描是不是跟聚集索引扫描一样,所要用的时间和资源与表扫描没有什么差别呢???

    大家可以看一下《SQLSERVER聚集索引与非聚集索引的再次研究(下)》本人做的一个小实验

    实验证明了《SQLSERVER企业级平台管理实践》里第363页说到的内容

    索引扫描表明SQLSERVER正在扫描一个非聚集索引。由于非聚集索引上一般只会有一小部分字段,所以这里虽然也是扫描,但是

    代价会比整表扫描要小很多

     ------------------------------------------------华丽的分割线--------------------------------------------------------------------

     这里有一个问题:没有主键但是有聚集索引,索引页的列数不一样,会多了一列,而这个列(uniquifier)的作用在下面会讲到

    这里创建Department3表

     View Code

     可以看到只有聚集索引没有主键的表会比主键表多了一列uniquifier列,这个列的作用会在创建Department5表的时候讲到

    -----------------------------------------------华丽的分割线-------------------------------------------------------

     下面说一下,复合主键或者聚集索引建立在多个字段上,KeyHashValue只会根据第一个字段生成hash key

    当你查询的时候where 后面的字段不包含创建聚集索引时的第一个字段或者复合主键的第一个字段就会聚集索引扫描而不是聚集索引查找

     创建Department4表

     View Code

    1 SELECT * FROM [dbo].[Department4] WHERE name='销售部6'  --聚集索引扫描  因为name不是复合主键中的第一个字段
    2 SELECT * FROM [dbo].[Department4] WHERE name='销售部241' AND [DepartmentID]=241  --聚集索引查找
    3 SELECT * FROM [dbo].[Department4] WHERE [DepartmentID]=241   --聚集索引查找

     

     

    在建立聚集索引的时候在多个字段上建立聚集索引是没有任何意义的

    因为聚集索引查找是根据建立索引的第一个字段来查找,索引扫描的时候会到数据页里扫描 ,而聚集索引的每一行只是一个数据页的范围值从而不能直接定位到要找的那条记录

    所以只需要在数据表的一个字段上建立聚集索引就可以了,而究竟要在哪一个字段上建立聚集索引大家一定好好斟酌,本人建议那一个字段在order by中经常要排序的

    因为数据页都已经按照聚集索引的第一个字段排好序的了

    而不像非聚集索引的索引页跟数据表的记录一一对应,扫描的时候扫描索引页的每一行

    大家可以对比一下聚集索引和非聚集索引页的结构

    聚集索引页的结构

    非聚集索引页的结构

    非聚集索引页面的结构会在SQLSERVER聚集索引与非聚集索引的再次研究(下)里讲到

     ---------------------------------------------------------华丽的分割线-----------------------------------------------------

     由于主键不允许重复值,那么就在表上创建一个不唯一的聚集索引,有人说在重复值很多的列上建立聚集索引没有意义

    创建Department5表  在Company字段上建立聚集索引,Company字段的值全部都是"中国你好有限公司XX分公司"

     View Code
     View Code

     

    在Department3表的时候讲到列(uniquifier),为什麽有主键的表没有这个列,而聚集索引的表有这个列,原因在于

    主键列不能有重复值,必须是唯一的,而聚集索引允许有重复值,所以聚集索引需要增加列(uniquifier)来区分重复值

    而且可以看到这里uniquifier列是没有规律的,不像Department表每隔120行记录在索引页里标记一行

    看一下执行计划和执行结果

    复制代码
     1 SET STATISTICS TIME ON
     2 SELECT * FROM [dbo].[Department5] WHERE [Company]='中国你好有限公司XX分公司' AND [DepartmentID]=241 
     3 
     4 SQL Server 分析和编译时间: 
     5    CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
     6 
     7 (1 行受影响)
     8 
     9 SQL Server 执行时间:
    10    CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
    复制代码

    1 SET STATISTICS TIME ON
    2 SELECT * FROM [dbo].[Department5] WHERE name='销售部106' AND [DepartmentID]=106  --聚集索引扫描
    3 SQL Server 执行时间:
    4    CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。

    至于应不应该在重复值很多的列上建立聚集索引我这里也不敢妄下判断,因为实际环境和这里的测试环境不一样

    在MSDN中的解释:http://msdn.microsoft.com/zh-cn/library/ms177484(v=SQL.105).aspx

    如果聚集索引不是唯一的索引,SQL Server 将添加在内部生成的值(称为唯一值)以使所有重复键唯一。此四字节的值对于用户不可见

    ----------------------------------------------------华丽的分割线-------------------------------------------------------

     堆表中的数据页之间[PrevPagePID],[NextPagePID]是否会首尾相连

    堆表

    聚集索引表

     -----------------------------------------------------华丽的分割线-------------------------------------------------

     聚集索引有一个特点,就是当表记录太少的时候不会生成任何索引页,当记录达到一定数量才生成索引页这个数量我现在还不清楚

    但是就算没有索引页,SQLSERVER还会使用聚集索引查找,这个问题的确奇怪

    创建Department6表,然后插入9条记录

    复制代码
     1 --只有聚集索引
     2 USE [pratice]
     3 GO
     4 CREATE TABLE Department6
     5 (
     6   DepartmentID INT IDENTITY(1, 1) NOT NULL ,
     7   Name NVARCHAR(200) NOT NULL ,
     8   GroupName NVARCHAR(200) NOT NULL ,
     9   Company NVARCHAR(300) ,
    10   ModifiedDate DATETIME NOT NULL DEFAULT ( GETDATE() ) ,
    11   CONSTRAINT [PK_Department6_1] PRIMARY KEY CLUSTERED
    12     (  Name ASC,DepartmentID ASC )
    13     WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
    14            ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]
    15 ) ON [PRIMARY]
    16 
    17 
    18 DECLARE @i INT
    19 SET @i=1
    20 WHILE @i < 10
    21     BEGIN
    22         INSERT  INTO Department6 ( name, [Company], groupname )
    23         VALUES  ( '销售部'+CAST(@i AS VARCHAR(200)), '中国你好有限公司XX分公司', '销售组' )
    24         SET @i = @i + 1
    25     END
    复制代码

    只有一个数据页和一个IAM页

    插入更多记录

    复制代码
    1 DECLARE @i INT
    2 SET @i=1
    3 WHILE @i < 100000
    4     BEGIN
    5         INSERT  INTO Department6 ( name, [Company], groupname )
    6         VALUES  ( '销售部'+CAST(@i AS VARCHAR(200)), '中国你好有限公司XX分公司', '销售组' )
    7         SET @i = @i + 1
    8     END
    复制代码

    开始有索引页了


    ---------------------------------------------华丽的分割线---------------------------------------------------

    大家再看一下Department2表的那部分,究竟数据页的排序顺序跟主键DepartmentID的排序顺序有没有关系呢?

    先创建Department7表,插入1000条记录

     View Code
    1 --TRUNCATE TABLE [dbo].[DBCCResult]
    2 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,Department7,-1) ')
    3 
    4 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 


    根据数据页的首尾连接顺序,我画了一下草图

    看一下索引页13791

     View Code

    再画一下草图

    对比一下数据页的首尾连接顺序那张图,不知道大家看出规律没有

    所以我把聚集索引结构图画成下面这个样子

    为什麽聚集索引只能按照第一个字段生成key?为什麽数据页只能按照第一个字段来排序?

    其实这个跟数据页排序有关的,大家再仔细看下面两张图

    聚集索引页里根据第一个字段排列好这些数据页的第一个字段的范围值,数据页根据这个范围值首尾相连一一排序好

    如果聚集索引按多个字段来排序,那么数据页根本排不了,多个字段又升序,又降序??那怎么排序啊?只能按照一个字段来排序

    聚集索引查找的时候,使用order by为什麽这么快,因为数据已经根据索引第一个字段排好序了,例子中的字段就是DepartmentID

    而只有非聚集索引的表order by的时候就需要排一下序了,因为表中没有聚集索引,数据页没有预先按照一定顺序来排序

    详细可以看一下非聚集索引的结构:SQLSERVER聚集索引与非聚集索引的再次研究(下)

    ---------------------------------------------华丽的分割线--------------------------------------------------------

    问题:为什么一个表只能建立一个聚集索引

    其实大家看一下我上面画的聚集索引结构图和非聚集索引结构图就知道了

    因为如果一个表有聚集索引,那么他的数据页跟索引页有非常强的联系,数据页跟主键第一个字段排好序了,例子中就是“DepartmentID”

    如果你再建一个聚集索引,你叫SQLSERVER应该按哪个字段来排序?排序方式是按照你原来的那个聚集索引的DepartmentID列来排序还是

    按照你新建的那个聚集索引的第一个字段来排序??

    多个聚集索引,数据页都按不同的字段顺序排序,来建立双向链表,那数据表不就乱套了???

    但是如果一个表中只有非聚集索引,非聚集索引里的索引页的每一行会有一个指针值指向数据页,数据页依然是堆,没有任何顺序可言

    所以你可以在一个表上建立多个非聚集索引也没问题

    至于表里面只有非聚集索引表结构是怎样的,大家可以看一下本系列的《SQLSERVER聚集索引与非聚集索引的再次研究(下)》

    到时大家就会更加清楚了o(∩_∩)o

    ----------------------------------------------华丽的分割线----------------------------------------------------------


    还有一个问题没有解决:

    为什麽根节点只有两行???是不是根节点只作连接作用,所以只有两行 ?

    聚集索引就说到这里了,有些地方有可能不对,希望大家强烈拍砖o(∩_∩)o

    也希望给个推荐o(∩_∩)o

     
    分类: sqlserver
  • 相关阅读:
    uva 10099(最大生成树+搜索)
    Codeforces Round #218 (Div. 2) 解题报告
    CodeChef December Challenge 2013 解题报告
    Codeforces Round #217 (Div. 2) 解题报告
    uva 1423 (拓扑排序)
    UESTC 1307 windy数(数位DP)
    Codeforces Round #216 (Div. 2) 解题报告
    Codeforces Round #215 (Div. 2) 解题报告
    uva 10047(BFS)
    uva 10369(最小生成树)
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3203142.html
Copyright © 2011-2022 走看看