Sql Server之旅——第四站 你必须知道的非聚集索引扫描

非聚集索引,这个是大家都非常熟悉的一个东西,有时候我们由于业务原因,sql写的非常复杂,需要join很多张表,然后就泪流满面了。。。这时候就

有DBA或者资深的开发给你看这个猥琐的sql,通过执行计划一分析。。。或许就看出了不该有的表扫描。。。万恶之源。。。然后给你在关键的字段加上非

聚集索引后。。。才发现提速比阿斯顿马丁还要快。。。那么一个问题来了,为什么非聚集索引能提速这么快。。。怎么做到的???是不是非常的好奇???

这篇我们来解开神秘面纱。

 

一:现象

      先让我们一睹非聚集索引的真容,看看到底在执行计划看来是个什么玩意。。。我这里有个product表,里面灌了8w多数据,然后在Name列上建立

一个非聚集索引,就像下图一样:

  

从上图中看到了两个好玩的东西,一个就是我想看到的“索引查找[nonclustered]”,这个大家很熟悉,也是这篇要说的,然后我们还看到了一个“RID查找”,

乍一看这是什么鸡巴玩意。。。非聚集索引跟它扯上什么关系了???

 

二:什么是RID

  通过前面几篇,我想大家都知道了数据页中的记录是如何寻找的?秘密就是通过slot槽位中的偏移量决定的,那问题来了,如果上升到数据页层面,我

只需要(pageID:slotID)就可以找到记录了,对不对?那如果我上升了文件层面,那是不是只需要知道(fileID:pageID:slotID)就可以找到数据页中的 

记录了?其实这里的RID就是站在文件的高度通过(fileID:pageID:slotID)找到表记录的。。。既RID=RowID=(fileID:pageID:slotID),如果你非要眼见

为实的话,在sq中l还真提供了这么个函数(sys.fn_PhysLocFormatter(%%physloc%%)),我们看下图:

看了上面的图,是不是很兴奋,一目了然,比如productID=18088这条记录,然来是在1号文件,34941号数据页,0号槽位上,productID=18089

是在1号槽位上,好了,当你知道RID是个什么东西的时候,我想你已经离彻底理解非聚集索引不远啦。。。

 

三:非聚集索引

  有一点我们肯定知道,就是非聚集索引是可以加速查找的,要是跟表扫描那样的龟速,那也就失去了索引的目的,既然能加速,是因为它和聚集索

引一样,在底层都玩起了B树,首先我们插入一些样例数据。

复制代码
 1 DROP TABLE dbo.Person
 2 
 3 CREATE TABLE Person(ID INT IDENTITY,NAME CHAR(900))
 4 CREATE  INDEX idx_Person_Name ON dbo.Person(Name)
 5 
 6 DECLARE @ch AS INT=97
 7 
 8 WHILE @ch<=122
 9 BEGIN
10     INSERT INTO dbo.Person VALUES(REPLICATE(CHAR(@ch),5))
11     SET @ch=@ch+1
12 END
复制代码

上面的sql,我故意在Name列设置为900个char,这也是索引的上限值,这样的话,我DBCC就可以导出很多数据页和索引页了。

可以看到,当我dbcc ind 的时候,发现Person表中已经有4个数据页,5个索引页,其中151号数据页是表跟踪页,174号为索引跟踪页,这也就

说明当我建立索引后,引擎给我们分配了专门的索引页来存放我们建立的Name索引,那下一步就是我们来看看这些索引中都存放着什么,这也是我

非常关心的,接下来我导出173号索引页。

1 DBCC PAGE(Ctrip,1,173,1)

复制代码
 1 Slot 0, Offset 0x60, Length 912, DumpStyle BYTE
 2 
 3 Record Type = INDEX_RECORD           Record Attributes =  NULL_BITMAP     Record Size = 912
 4 
 5 Memory Dump @0x000000000EF1C060
 6 
 7 0000000000000000:   16616161 61612020 20202020 20202020 †.aaaaa           
 8 0000000000000010:   20202020 20202020 20202020 20202020 9 0000000000000020:   20202020 20202020 20202020 2020202010 0000000000000030:   20202020 20202020 20202020 2020202011 0000000000000040:   20202020 20202020 20202020 2020202012 0000000000000050:   20202020 20202020 20202020 2020202013 0000000000000060:   20202020 20202020 20202020 2020202014 0000000000000070:   20202020 20202020 20202020 2020202015 0000000000000080:   20202020 20202020 20202020 2020202016 0000000000000090:   20202020 20202020 20202020 2020202017 00000000000000A0:   20202020 20202020 20202020 2020202018 00000000000000B0:   20202020 20202020 20202020 2020202019 00000000000000C0:   20202020 20202020 20202020 2020202020 00000000000000D0:   20202020 20202020 20202020 2020202021 00000000000000E0:   20202020 20202020 20202020 2020202022 00000000000000F0:   20202020 20202020 20202020 2020202023 0000000000000100:   20202020 20202020 20202020 2020202024 0000000000000110:   20202020 20202020 20202020 2020202025 0000000000000120:   20202020 20202020 20202020 2020202026 0000000000000130:   20202020 20202020 20202020 2020202027 0000000000000140:   20202020 20202020 20202020 2020202028 0000000000000150:   20202020 20202020 20202020 2020202029 0000000000000160:   20202020 20202020 20202020 2020202030 0000000000000170:   20202020 20202020 20202020 2020202031 0000000000000180:   20202020 20202020 20202020 2020202032 0000000000000190:   20202020 20202020 20202020 2020202033 00000000000001A0:   20202020 20202020 20202020 2020202034 00000000000001B0:   20202020 20202020 20202020 2020202035 00000000000001C0:   20202020 20202020 20202020 2020202036 00000000000001D0:   20202020 20202020 20202020 2020202037 00000000000001E0:   20202020 20202020 20202020 2020202038 00000000000001F0:   20202020 20202020 20202020 2020202039 0000000000000200:   20202020 20202020 20202020 2020202040 0000000000000210:   20202020 20202020 20202020 2020202041 0000000000000220:   20202020 20202020 20202020 2020202042 0000000000000230:   20202020 20202020 20202020 2020202043 0000000000000240:   20202020 20202020 20202020 2020202044 0000000000000250:   20202020 20202020 20202020 2020202045 0000000000000260:   20202020 20202020 20202020 2020202046 0000000000000270:   20202020 20202020 20202020 2020202047 0000000000000280:   20202020 20202020 20202020 2020202048 0000000000000290:   20202020 20202020 20202020 2020202049 00000000000002A0:   20202020 20202020 20202020 2020202050 00000000000002B0:   20202020 20202020 20202020 2020202051 00000000000002C0:   20202020 20202020 20202020 2020202052 00000000000002D0:   20202020 20202020 20202020 2020202053 00000000000002E0:   20202020 20202020 20202020 2020202054 00000000000002F0:   20202020 20202020 20202020 2020202055 0000000000000300:   20202020 20202020 20202020 2020202056 0000000000000310:   20202020 20202020 20202020 2020202057 0000000000000320:   20202020 20202020 20202020 2020202058 0000000000000330:   20202020 20202020 20202020 2020202059 0000000000000340:   20202020 20202020 20202020 2020202060 0000000000000350:   20202020 20202020 20202020 2020202061 0000000000000360:   20202020 20202020 20202020 2020202062 0000000000000370:   20202020 20202020 20202020 2020202063 0000000000000380:   20202020 20940000 00010000 00020000 †     ........... 
复制代码
复制代码
1 Row - Offset                         
2 7 (0x7) - 6480 (0x1950)              
3 6 (0x6) - 5568 (0x15c0)              
4 5 (0x5) - 4656 (0x1230)              
5 4 (0x4) - 3744 (0xea0)               
6 3 (0x3) - 2832 (0xb10)               
7 2 (0x2) - 1920 (0x780)               
8 1 (0x1) - 1008 (0x3f0)               
9 0 (0x0) - 96 (0x60)     
复制代码

 

从上面至少可以发现三个有趣的现象:

<1>:173号索引页中slot0和slot1槽位指向记录的内容已经有序了,比如:aaaaa,bbbbb。。。。这样。。。。原来非聚集索引也是有序呀。。。

<2>:6161616161就是16进制的aaaaa。

    9400000001000000 :这几个数字非常重要,因为是16进制表示,所以2位16进制表示一个字节,所以可以这么解释,前面4个字节表示

    pageID,中间2个字节表示fileID,后面2个字节表示slot,看到这里你是不是想起了RID。。。因为RID就是这三样的组合。。。原来非聚集索

   引的记录存放的就是“key+RowID”呀。。。。

<3>:通过最后的槽位列表,可以得知173号索引页上存放着8条索引记录。

 

  好了,看完了叶子节点,我们再看分支节点,也就是IndexLevel=1的那条索引数据页,也就是78号。ok,dbcc看看吧。

当看到这个列表的时候,不知道你脑子里面是不是有一幅图出来了,就像上一篇看到聚集索引一样,因为它的结构和聚集索引非常像,只不过

非聚集索引这里多了一个RID而已。。。最后我也把图贡献一下。

 

总结一下:在走非聚集索引的时候,比如你的条件是where name='jjjjj' 时,它的逻辑是这样的,根据78号索引数据页的key的范围,然后通过

      rowid走到了79号索引数据页,然后在79号索引数据页中顺利的找到了jjjjj,这时候就可以拿出jjjjj的rowid去表数据页中直接定位记录,

      最后输出。。。。。这个也就是博客开头的地方为什么会出现RID的查找。。。

 

分类:  sql server
31
5
(请您对文章做出评价)
« 上一篇: Sql Server之旅——第三站 解惑那些背了多年聚集索引的人
» 下一篇: Sql Server之旅——第五站 确实不得不说的DBCC命令(文后附年会福利)
posted @  2015-01-23 01:24  一线码农 阅读( 4336) 评论( 42编辑  收藏

  
#1楼 2015-01-23 07:20  上帝之城   
LZ加油,
  
#2楼 2015-01-23 08:48  请叫我进击的小学生   
虽然不懂,但感觉很牛逼的样子
  
#3楼 2015-01-23 09:00  codezyc   
先顶再看
  
#4楼 2015-01-23 09:01  acdomain   
javascript研究jint,数据库研究ravendb,都是.net的。
  
#5楼 2015-01-23 09:27  firstrose   
这个表是堆表么?
  
#6楼 2015-01-23 09:32  寻找和谐   
很深入,比小学生普及课文好多了,推荐+1
  
#7楼 [ 楼主2015-01-23 09:32  一线码农   
@ firstrose
表还是堆表,name索引页已经是有序的b树结构了。
  
#8楼 [ 楼主2015-01-23 09:33  一线码农   
@ acdomain
谢谢提醒
  
#9楼 [ 楼主2015-01-23 09:33  一线码农   
@ codezyc
顶顶顶
  
#10楼 [ 楼主2015-01-23 09:34  一线码农   
@ 请叫我进击的小学生
当你知道数据页的结构后,这些都不是问题,第二篇有说
  
#11楼 [ 楼主2015-01-23 09:35  一线码农   
@ 上帝之城
加油加油
  
#12楼 2015-01-23 09:36  请叫我进击的小学生   
@ 一线码农
我会继续关注的,谢谢
  
#13楼 2015-01-23 09:51  小宝er   
虽然不懂,但感觉很牛逼的样子
  
#14楼 2015-01-23 10:10  dotNetDR_   
聚集索引能单独放到和表数据不一样的文件组吗?
我知道非聚集索引可以这样子做。
  
#15楼 2015-01-23 10:46  firstrose   
@ 一线码农

在下的意思是,具体到这个表,出现RID的原因不单单是由于非聚集索引,而是堆表+非聚集索引
  
#16楼 2015-01-23 10:47  firstrose   
@ dotNetDR_

由聚集索引的原理可得,不行
  
#17楼 [ 楼主2015-01-23 10:51  一线码农   
@ 小宝er
去去去
  
#18楼 [ 楼主2015-01-23 11:07  一线码农   
@ dotNetDR_
引用 聚集索引能单独放到和表数据不一样的文件组吗?
我知道非聚集索引可以这样子做。


其实从上一篇可以看到,给表做聚集索引时,再dbcc的时候,发现表数据页不见了,而只存在聚集索引数据页,也就说明表数据页的内容都导到聚集索引页中去了,应该可以证明这时候聚集索引数据页就是表数据页了,比如后续再做非聚集索引的时候,这时候非聚集索引列中存放的不再是rowid了,而是聚集索引列值,而原始的表数据页应该是遗弃不用了,当然不知道是否还有其他用途,这时候你用文件组的方式把这个聚集索引放到区别于原始表数据页的fileid的另外一个fileid中去,理论上应该是可以的,不过没研究过应该怎么设置。
  
#19楼 [ 楼主2015-01-23 11:14  一线码农   
@ firstrose
引用 @一线码农

在下的意思是,具体到这个表,出现RID的原因不单单是由于非聚集索引,而是堆表+非聚集索引

你说的非常对,前提是堆表,如果是有聚集索引的话,那就是 key+clusteredのkey
  
#20楼 [ 楼主2015-01-23 12:26  一线码农   
@ 寻找和谐
谢谢支持~
  
#21楼 2015-01-23 13:21  门外客   
大牛,我们做朋友吧???
  
#22楼 2015-01-23 13:50  风影极光   
深入浅出的讲明白的了非聚集索引的工作原理!赞!
  
#23楼 2015-01-23 15:10  有点   
总是整不明白索引,也用不太明白
  
#24楼 [ 楼主2015-01-23 15:21  一线码农   
@ 曹全阳
牛毛哦,加群就好了
  
#25楼 [ 楼主2015-01-23 15:23  一线码农   
@ 风影极光
大概知道底层原理对认识高层的执行计划有帮助,更多的细节放在后面说,感谢关注
  
#26楼 [ 楼主2015-01-23 15:25  一线码农   
@ 有点
那就花点时间整明白,这样你就对上层的一些花拳绣腿胸有成竹了。
  
#27楼 2015-01-23 15:49  11点后要睡觉   
  
#28楼 2015-01-23 17:02  指尖流淌   
两点问题:
1、开篇所提到的加上非聚集索引而引起性能提升和文章标题非聚集索引扫描一毛钱关系没有........不要让新手产生误导...
2、关于所谓的“万恶的表扫描”,并不是所有的性能问题都是表扫描导致的,而且很多给新手灌输的想提高性能就走索引,我不否认这种方式在某种环境下的有效性... 但是,表扫描方式也是一种很优质的获取数据方式....比如:相比table scan和clustered index scan、table scan和nonclustered index scan、这时候“表扫描是可爱的”...
  
#29楼 2015-01-23 17:31  抬头45度   
LZ您好,看了您的几篇文章收益匪浅,也是实际中碰到的,关于索引的使用在大表中查询速度的问题,仍然有些困惑,我刚把我遇到的问题发到博问上了,请大神帮帮看看, http://q.cnblogs.com/q/69437/,谢谢了
  
#30楼 2015-01-23 17:32  紫川帝林   
我发现爆粗口的文章讲解的东西都非常直观明了!我喜欢!
  
#31楼 [ 楼主2015-01-23 17:44  一线码农   
@ 指尖流淌
引用 两点问题:
1、开篇所提到的加上非聚集索引而引起性能提升和文章标题非聚集索引扫描一毛钱关系没有........不要让新手产生误导...
2、关于所谓的“万恶的表扫描”,并不是所有的性能问题都是表扫描导致的,而且很多给新手灌输的想提高性能就走索引,我不否认这种方式在某种环境下的有效性... 但是,表扫描方式也是一种很优质的获取数据方式....比如:相比table scan和clustered index scan、table scan和nonclustered index scan、这时候“表扫描是可爱的”...


1:开篇不是让大家对什么是非聚集索引扫描有个大概的认识么~~~图文结合。。。
2:我所说的表扫描是因为他的复杂度是最烂的O(N),有了索引它的复杂度为LogmN了,当然事情没有绝对性,你如果滥用索引的话,确实会出现比表扫描还烂的情况,比如状态单一的字段加索引,大字段等等。。。
  
#32楼 [ 楼主2015-01-23 18:39  一线码农   
@ 紫川帝林
好吧
  
#33楼 2015-01-23 20:57  指尖流淌   
@ 一线码农

好吧,关键是你整篇都没有在讲非聚集索引扫描...还图文结合....
滥用索引的话,对于查询基本是没有影响的,顶多就是索引失效罢了...对于表扫描没有烂不烂这一说....这是很多开发人员甚至DBA的最常见的误区之一

当然,没有针对楼主的意思..重在纠正问题.....
  
#34楼 2015-01-23 23:10  Grart   
很好!通俗易懂。楼主能科普下DBCC命令吗
  
#35楼 2015-01-27 16:30  我有一颗四叶草   
你在携程上班?.咋个感觉右边一直是广告.
  
#36楼 2015-01-29 22:32  会长   
博主,结合上一篇文章,聚集索引和普通索引的区别就是:前者存放索引的叶子节点页中同时存放数据,后者存放索引的叶子节点页中存放的是数据所在位置(RID),如果根据索引查找数据,后者比前者多一个步骤(这个步骤是根据RID找到数据位置),是不是可以理解为聚集索引会比普通索引带来更快的查询速度。应该把最常作为查询条件的列设置为聚集索引列?谢谢。
  
#37楼 [ 楼主2015-01-29 22:57  一线码农   
@ 会长
“聚集索引会比普通索引带来更快的查询速度” 这句话不完全正确,就看你怎么比了,一般四种情况:
前提:where 中的列值

第一:当你用聚集索引的列值和非聚集索引的列值比where,那么聚集快。
第二:聚集索引非列值和非聚集索引的列值比where,那么非聚集快。
因为前者无法走B树,只能逐行扫描,后者直接走B树来定位RID。
第三:....
第四:....

理论上是这么说,但是实际上你怎么好预判哪个是最常查询的列,替代方法
有很多,在非聚集索引上做索引覆盖,或者多个窄索引来做连接索引。
  
#38楼 2015-01-29 23:07  会长   
@ 一线码农
谢谢回复
  
#39楼 2015-04-30 23:53  阿赫瓦里   
大牛,我正打算好好研究SQL Server,赞一个!!
  
#40楼 2015-10-21 16:16  小渥   
@一线码农 开过阿斯顿马丁没有啊?开个玩笑呵
  
#41楼 2016-05-18 11:46  symmeryTrees   
fn_PhysLocFormatter是不是存在解析错误啊,我的数据行A,明明在PageId为163的数据页内,但用这个函数解析出来的RID竟然是(1:41728:0),费解
  
#42楼 2016-05-25 17:12  徐良1992   
博主,介意我转载吗?我也是留着备用。 你这些文章真心赞。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值