SQL Server索引的原理深入解析

前言

此文是我之前的笔记整理而来,以索引为入口进行探讨相关数据库知识(又做了修改以让人更好消化)。SQL Server接触不久的朋友可以只看以下蓝色字体字,简单有用节省时间;如果是数据库基础不错的朋友,可以全看,欢迎探讨。

索引的概念

索引的用途:我们对数据查询及处理速度已成为衡量应用系统成败的标准,而采用索引来加快数据处理速度通常是最普遍采用的优化方法。

索引是什么:数据库中的索引类似于一本书的目录,在一本书中使用目录可以快速找到你想要的信息,而不需要读完全书。在数据库中,数据库程序使用索引可以重啊到表中的数据,而不必扫描整个表。书中的目录是一个字词以及各字词所在的页码列表,数据库中的索引是表中的值以及各值存储位置的列表。

索引的利弊:查询执行的大部分开销是I/O,使用索引提高性能的一个主要目标是避免全表扫描,因为全表扫描需要从磁盘上读取表的每一个数据页,如果有索引指向数据值,则查询只需要读少数次的磁盘就行啦。所以合理的使用索引能加速数据的查询。但是索引并不总是提高系统的性能,带索引的表需要在数据库中占用更多的存储空间,同样用来增删数据的命令运行时间以及维护索引所需的处理时间会更长。所以我们要合理使用索引,及时更新去除次优索引。

1.聚集索引和非聚集索引

索引分为聚集索引和非聚集索引

1.1 聚集索引

表的数据是存储在数据页中(数据页的PageType标记为1),SqlServer一页是8k,存满一页就开辟下一页存储。如果表有聚集索引,那么一笔一笔物理数据就是按聚集索引字段的大小升/降排序存储在页中。当对聚集索引字段更新或中间插入/删除数据时,都会导致表数据移动(造成性能一定影响),因为它要保持升/降排序。

注意,主键只是默认是聚集索引,它也可以设置为非聚集索引,也可以在非主键字段上设置为聚集索引,全表只能有一个聚集索引。

一个优秀的聚集索引字段一般包含以下4个特性:

(A).自增长

总是在末尾增加记录,减少分页和索引碎片。

(B).不被更改

减少数据移动。

(C).唯一性

唯一性是任何索引最理想的特性,可以明确索引键值在排序中的位置。

更重要的是,索引键指唯一的话,它在每条记录里才可以正确指向源数据行RID。如果聚集索引键值不唯一,SqlServer就需要内部生成uniquifier 列组合当作聚集键保证“键值”唯一性;如果非聚集索引键值不唯一,就会增加RID列(聚集索引键或者堆表中的行指针)保证“键值”唯一性。

思考(可略过):索引“键值”在非叶子节点也有保证唯一性,原因应该是为了明确索引记录在非叶子节点中的位置。比如有个非聚集索引字段Name2,表中有很多Name2='a'的记录,导致Name2='a'在非叶子节点上有多条索引记录(节点),这时候再insert一笔Name2=‘a'的记录时,就可以根据非叶子节点的RID和新增记录的RID很快确定要insert到哪个索引记录(节点)上,如果没有非叶子节点的RID,那得遍历到所有Name2='a'的叶子节点才能确定位置。另外,当我们select * from Table1 where Name2<='a'时,返回的数据是按非聚集索引Name2和RID排序的,很好理解返回的数据就是按这边索引存储的顺序排序的。这是这条sql查询时有用到Name2索引的结果,如果数据库查询计划因“临界点”问题选择直接表数据扫描,那返回的数据默认就是按表数据的顺序排序的。

为了“键值”唯一性,对于聚集索引,uniquifier 列只在索引值重复时增加。对于非聚集索引,如果创建索引时没定义唯一,RID会在所有记录增加,就算索引值是唯一的;如果创建索引时定义唯一,RID只在叶子层增加,用于查找源数据行,即书签查找操作。

(D).字段长度小

聚集索引键长度越小,一页索引页就可以容纳更多索引记录,进而减少索引B树结构的深度。例如,一个百万记录的表有一个int聚集索引,可能只需要3层的B树结构。如果把聚集索引定义在更宽的列(比如uniqueidentifier列需要16 字节),那么索引的深度会增加到4层。任何聚集索引查找需要4个I/O操作(确切的说是4个逻辑读),原先只要3个I/O操作。
同样,非聚集索引里会包含聚集索引键值,聚集索引键长度越小非聚集索引记录也就越小,一页索引页就可以容纳更多索引记录。

1.2 非聚集索引

也是存储在页中(PageType标记为2的页,叫索引页)。比如表T建立了一个非聚集索引Index_A,那么表T有100条数据的话,那么索引Index_A也就有100条数据(准确的说是100条叶子节点数据,索引是B树结构,如果树的高度大于0,那么就有根节点页或中间节点页数据,这时索引数据就超过100条),如果表T还有非聚集索引Index_B,那么Index_B也是至少100条数据,所以索引建越多开销越大。

更新索引字段、插入一条数据、删除一条数据都会造成索引的维护从而造成性能的一定影响。在不同情况下,性能影响是不同的。比如当你有一个聚集索引,插入的数据又都是在末尾,这样几乎是不会造成数据移动,影响较小;如果插入的数据在中间位置,一般会导致数据移动,而且可能产生分页和页碎片,影响就会稍大一点(如果插入到的中间页有足够的剩余空间容纳插入的数据,而且位置是在页末,也是不会造成数据移动)

2.索引的结构

都说SqlServer的索引是B树结构(这边假定你对B树结构有一定了解),那它到底长什么个模样呢,可以用Sql语句来查看它的逻辑呈现。

新建查询执行语法: DBCC IND(Test,OrderBo,-1) --其中Test库的OrderBo表有1万笔数据,有聚集索引Id主键字段
(不妨自己动手建个表,有聚集索引字段,插入1万表数据,然后执行这个语法看看,会收获很多,百闻不如一见)

执行结果:

如上图,看到一个IndexLevel=2的索引页2112(这边它就是B树的根节点,IndexLevel最大的就是根节点,往下就是子级、子子级...只有一个根页作为B树结构的访问入口点),说明一定还有IndexLevel=1的索引页和IndexLevel=0的叶子页。由于这边是聚集索引,因此当IndexLevel=0的叶子页就是数据页,存储的是一笔一笔的物理数据。如上图也可以看到,IndexLevel=0的行的PageType等于1,就是代表数据页,上面1.1章节讲到聚集索引时,也有提到PageType=1;而如果是非聚集索引,IndexLevel=0的叶子页,PageType是等于 2,仍然是索引页。

同样,我们用Sql命令DBCC PAGE看一看

-- DBCC TRACEON(3604,-1)
DBCC PAGE(Test,1,2112,3)
 --根节点2112,可以查出它的两个子节点2280和2448,然后对这两个子节点再作DBCC PAGE查询
DBCC PAGE(Test,1,2280,3)
DBCC PAGE(Test,1,2448,3)

如上图,IndexLevel=2的2112页有两个IndexLevel=1的子节点2280和2448,子节点下又有子节点,每个节点负责不同的索引键值的区间(即上图的“Id(key)”栏位,第一行值是Null,表示最小值或倒序时的最大值)。这样的层级关系是不是就是一棵B树结构,其中IndexLevel其实就是B树结构中的高度Height。

SqlServer在索引中查找某一笔记录时,是从根节点往下找到叶子节点,因为所有数据地址都有存在叶子节点,这其实是B+树的特点之一(B树特点是如果查找的值在非叶子节点就找到,则就能直接返回,显然SqlServer不是这么做,要验证这一点你可以set statistics io on把统计开起来,然后select看下逻辑读的次数)。

既然一定会找到叶子节点,那么索引包含列只要在叶子节点记录就可以了,即非叶子节点没有记录包含列,“索引包含列”见下文第3章节。

B+树这个特点(所有数据地址都有存在叶子节点)也利于between value1 and value2 区间查询,只要找到value1和value2(在叶子节点),然后把中间串起来就是要的结果了。

SqlServer索引结构更像是B+树,最终是B树和B+树的混合版,数据结构都是人定的,不一定就是纯粹的B树或者单纯的B+树。

3.索引包含列和书签查找

谈到索引,这边再讲一个SqlServer2005开始增加的“索引包含列”功能,很实用。

比如,在大报表查询数据时,where条件用到索引字段Name2,但是要select的字段是Name1,这时候可以使用“索引包含列”把Name1包含在索引字段Name2中,大大提高查询性能。

语法: Create [UNIQUE]  Nonclustered/Clustered Index IndexName On dbo.Table1(Name2) Include(Name1);

接下来分析为什么索引包含列可以大大提高性能。仍然使用DBCC PAGE命令,查看一个非聚集索引并有包含列的索引数据情况:

由上图可知,包含列Name1也存储在索引数据中。因此,当数据库用索引字段Name2定位到要查找的某一行时,就可以直接把Name1的值返回了,而不用再根据RID(上图是【HEAP RID(Key)】列)定位到数据页中去取值,即减少了书签查找。当查询只返回一条数据,只有一次书签查找时当然没什么,如果查询返回的数据很大,每一笔都要去数据页找数据取出来,1000笔就是1000次书签查找,可想而知性能消耗很大,这时候“索引包含列”价值就大大体现出来了。

关于一次书签查找,表有聚集索引(比如Id)时就是类似执行了一次 select Name1 from Table1 where Id=1 ,利用聚集索引键Id查找(查找方式就是索引Id的B树结构查找),而如果表没有聚集索引,则是根据数据行指针(由“文件号2byte:页号4byte:槽号2byte”组成)查找。聚集索引键和行指针一般统称为RID(Row ID)指针。从这里我们可以想到,如果你的表没有很好的聚集索引字段,建议自增长的Id字段做聚集索引主键(冗余出Id字段也行),它符合自增长、不被更改、唯一性、长度小的特性,是聚集索引的很好选择。

自增长Id绝大部分情况下是适用的,特殊的情况看具体需求而定吧。还有自增长Id要考虑一个缺陷,当对表大数据量的并发insert记录时,可以想象每个线程都是要insert到末尾那个页,就会发生竞争和等待。解决这种情况你可以用uniqueidentifier类型字段(16字节,我是不建议使用)或者哈希分区(就是一个表分成多个表,大数据处理中分库分表是正常的)等。但是我建议先优化你的insert效率(insert性能本身是很快的),测试每秒并发insert数是否满足生产环境,以保留简单稳定高效的自增长Id作法。

自增长Id不一定就是用数据库提供的自增长,你也可以自己写算法生成一个并发情况下也能唯一的Id(这时候一般长度是bitint,8字节整形),这种情况适合场景是分布式数据库中主从复制时Id栏位是要求一定不能出错的情况(主从复制的一般模式下,主库的Id是按主库增长,从库Id也是按从库自己的增长,如果遇到死锁等原因导致主从复制不同步时,那从库的Id就和主库的Id自增长就对不上号了)。如果自增长Id是冗余出的主键,那主从库Id对不上号也就无影响。

另外,上图最后一列【Row Size】还告诉我们,索引列或索引包含列的size不要太长,否则一页容不了几笔记录,这样大大增加了索引页数量,而且索引数据所占的空间也大大增加了。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • SQL Server2014 哈希索引原理详解

    当一个key-value键值对传递给一个哈希函数的时候,经过哈希函数的计算之后,根据结果会把key-value键值对放在合适的hash buckets(哈希存储桶)里 举个栗子 我们假设对10取模( % 10 )就是哈希函数.如果key-value键值对的key是1525 ,传递到哈希函数,那么1525 会存放在第五个bucket里 因为5 as 1525 % 10 = 5. 同样,537 会存放在第七个bucket ,2982 会存放在第二个bucket ,依次类推 同样,在hash inde

  • SQL Server全文索引服务

    SQL 7的全文检索和Index Server的检索方式非常类似. Contains  AND, OR, NOT 可以在Contains中很方便使用逻辑表达式 Example: Select username from member where contains(userinfo,'"作家" AND "木匠"') Select username from member where contains(userinfo,'"作家" OR "

  • sqlserver索引的原理及索引建立的注意事项小结

    聚集索引,数据实际上是按顺序存储的,数据页就在索引页上.就好像参考手册将所有主题按顺序编排一样.一旦找到了所要搜索的数据,就完成了这次搜索,对于非聚集索引,索引是安全独立于数据本身结构的,在索引中找到了寻找的数据,然后通过指针定位到实际的数据. SQL Server中的索引使用标准的B-树来存储他们的信息,如下图所示,B-树通过查找索引中的一个关键之来提供对于数据的快速访问,B-树以相似的键记录聚合在一起,B不代表二叉(binary),而是代表balanced(平衡的),而B-树的一个核心作用就

  • SQL Server 索引介绍

    一,索引的概述 1,概念: 数据库索引是对数据表中一个或多个列的值进行排序的结构,就像一本书的目录一样,索引提供了在行中快速查询特定行的能力. 2,优缺点: 2.1优点: 1,大大加快搜索数据的速度,这是引入索引的主要原因. 2,创建唯一性索引,保证数据库表中每一行数据的唯一性. 3,加速表与表之间的连接,特别是在实现数据的参考完整性方面特别有意义. 4,在使用分组和排序子句进行数据检索时,同样可以减少其使用时间. 2,2缺点: 1,索引需要占用物理空间,聚集索引占的空间更大. 2,创建索引和维

  • SQLSERVER 创建索引实现代码

    什么是索引 拿汉语字典的目录页(索引)打比方:正如汉语字典中的汉字按页存放一样,SQL Server中的数据记录也是按页存放的,每页容量一般为4K.为了加快查找的速度,汉语字(词)典一般都有按拼音.笔画.偏旁部首等排序的目录(索引),我们可以选择按拼音或笔画查找方式,快速查找到需要的字(词). 同理,SQL Server允许用户在表中创建索引,指定按某列预先排序,从而大大提高查询速度. • SQL Server中的数据也是按页(4KB)存放 • 索引:是SQL Server编排数据的内部方法.它

  • SQLSERVER聚集索引和主键(Primary Key)的误区认识

    很多人会把Primary Key和聚集索引搞混起来,或者认为这是同一个东西.这个概念是非常错误的. 主键是一个约束(constraint),他依附在一个索引上,这个索引可以是聚集索引,也可以是非聚集索引. 所以在一个(或一组)字段上有主键,只能说明他上面有个索引,但不一定就是聚集索引. 例如下面: 复制代码 代码如下: USE [pratice] GO CREATE TABLE #tempPKCL ( ID INT PRIMARY KEY CLUSTERED --聚集索引 ) ---------

  • sqlserver 索引的一些总结

    1.1.1 摘要 如果说要对数据库进行优化,我们主要可以通过以下五种方法,对数据库系统进行优化. 1. 计算机硬件调优 2. 应用程序调优 3. 数据库索引优化 4. SQL语句优化 5. 事务处理调优 在本篇博文中,我们将想大家讲述数据库中索引类型和使用场合,本文以SQL Server为例,对于其他技术平台的朋友也是有参考价值的,只要替换相对应的代码就行了! 索引使数据库引擎执行速度更快,有针对性的数据检索,而不是简单地整表扫描(Full table scan). 为了使用有效的索引,我们必须

  • SQLSERVER全文目录全文索引的使用方法和区别讲解

    先介绍一下SQLSERVER中的存储类对象,哈哈,先介绍一下概念嘛,让新手老手都有一个认知SQLSERVER Management Studio将[全文目录].[分区函数]以及[分区方案]节点纳入其[对象资源管理器]的[存储]节点之中,如下图所示: 全文目录 数据库[存储]|[全文目录]节点是用于保存和管理[全文索引]的节点.全文目录通常是由同一数据库中的零个或多个数据表的全文索引构成的.需要注意的是,只能为每个数据表创建一个全文索引.因此,一旦在某个数据表上创建了全文索引,那么该数据表将只能隶

  • SQL Server 聚集索引和非聚集索引的区别分析

    聚集索引:物理存储按照索引排序非聚集索引:物理存储不按照索引排序优势与缺点聚集索引:插入数据时速度要慢(时间花费在"物理存储的排序"上,也就是首先要找到位置然后插入)查询数据比非聚集数据的速度快 汉语字典的正文本身就是一个聚集索引.比如,我们要查"安"字,就会很自然地翻开字典的前几页,因为"安"的拼音是"an",而按照拼音排序汉字的字典是以英文字母"a"开头并以"z"结尾的,那么&quo

  • SqlServer索引的原理与应用详解

    索引的概念 索引的用途:我们对数据查询及处理速度已成为衡量应用系统成败的标准,而采用索引来加快数据处理速度通常是最普遍采用的优化方法. 索引是什么:数据库中的索引类似于一本书的目录,在一本书中使用目录可以快速找到你想要的信息,而不需要读完全书.在数据库中,数据库程序使用索引可以重啊到表中的数据,而不必扫描整个表.书中的目录是一个字词以及各字词所在的页码列表,数据库中的索引是表中的值以及各值存储位置的列表. 索引的利弊:查询执行的大部分开销是I/O,使用索引提高性能的一个主要目标是避免全表扫描,因

随机推荐