SQL Server索引有什么用

64次阅读
没有评论

共计 4781 个字符,预计需要花费 12 分钟才能阅读完成。

这篇文章给大家分享的是有关 SQL Server 索引有什么用的内容。丸趣 TV 小编觉得挺实用的,因此分享给大家做个参考,一起跟随丸趣 TV 小编过来看看吧。

索引的概念

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

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

索引的利弊:查询执行的大部分开销是 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 不要太长,否则一页容不了几笔记录,这样大大增加了索引页数量,而且索引数据所占的空间也大大增加了。

感谢各位的阅读!关于“SQL Server 索引有什么用”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-28发表,共计4781字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)