SQL Server索引进阶:第十级,索引内部结构

原文地址:

Stairway to SQL Server Indexes: Level 10,Index Internal Structure

本文是SQL Server索引进阶系列(Stairway to SQL Server Indexes)的一部分。

在之前的级别中,我们从逻辑的角度介绍索引,集中于它们能为我们做什么。现在,是时候从物理的角度,并且检查一下索引的内部结构,从理解索引的内部结构,引导我们理解索引在上层做的工作。通过索引的结构,它是如何维护的,你可以理解在进行插入,更新,删除的时候,最小化索引的创建,修改,移动。

因此,从现在开始,我们除了要关心索引带来的好处,还要关心索引的消耗。毕竟,最小化消耗可以带来最大化的好处,带来最大化的好处是本系列的宗旨。

叶子和非叶子层

索引的机构由叶子和非叶子层组成。尽管没有明显的说明,我们之前的级别主要集中于索引的叶子层。因此,聚集索引的叶子层就是表本身,每个叶子层的入口都是表中的一行。对于非聚集索引来说,在叶子层每行都有一个入口(过滤索引除外),每个入口由索引键列,可选的包含列,以及标签组成,标签的内存是聚集索引的键列,或者RID(Row ID)。

索引入口也叫做索引行,不管它是表的一行(聚集索引叶子入口),还是表中一行的引用(非聚集索引叶子层),还是指向更低级别(非叶子层)的一页。

非叶子层是构建在叶子层上的结构,使得SQL Server可以完成下面的工作:

  • 以索引键的顺序维护索引的入口。
  • 根据给定的索引键值,快速的找到叶子层。

在第一级中,我们用电话本来介绍索引的好处。在电话本中,名叫“Meyer,Helen”的人,因为电话本是按照last name排序的,因此我们知道这个人应该再中间位置,直接跳到电话本的中间位置开始查找。但是SQL Server没有这种知识。它不知道哪一页是中间页,除非它从索引的开始访问到结束。因此,SQL Server在索引中构建了一些额外的结构。

非叶子层

这些额外的结构叫做非叶子层,也叫节点层。是构建在叶子层上的,不论页的物理位置在哪里。目的是给SQL Server指出每个索引的单独的页入口点,从一页到另一页的较短路劲。

在索引中的每一页,不管他是哪一层,都包含索引行或者入口。在叶子层的页中,每个入口点都指向表中的一行,或者就是表中的一行。如果表有十亿行数据,索引的叶子层就会有十亿个入口。

紧挨着叶子层的上一层,是最低的非叶子层,他的每一个入口都指向一个叶子层的页。如果我们的十亿个入口,平均每页有100个入口,叶子层将包含1,000,000,000/100=10,000,000页。如果最低的非叶子层包含10,000,000个入口,每个都指向叶子层的页,将包含10,000,000/100=100,000页。

每个较高的非叶子层的页中的入口,都指向下一层的页。因此,下一个较高的非叶子层就会有100,000个入口,1000个页。在上一层,就会是1000个入口,10页,再往上就是10个入口,1页,这就是最上面了。

索引顶端的页叫做根页。索引中,在根页之下,在叶子层之上的层叫做中间层。层数从0开始(叶子层就是0)向上增加。因此,中间层至少也是1.

非叶子层的入口只包含索引键的列和指向下一层页的指针。索引的包含列只存在于叶子层的入口,非叶子层的入口中没有这类信息。

索引中的每一页,除去根页,都包含两个额外的指针。一个指向下一页,一个指向上一页。页的双向链的结果就是,使得SQL Server可以正向或者反向扫描任何一层的页面。

简单例子

通过上图,可以说明索引的树形结构。我们在Personnel.Employee表的LastName和FirstName列创建了索引。

CREATE NONCLUSTERED INDEX IX_Full_Name
ON Personnel.Employee
(
LastName,
FirstName,
)
GO 

指向页的指针除了包含页的编号,还包含数据文件的编号。如果一个指针是5:4567,表示指向#5文件的第4567页。

为了清除和明白,上面的索引和实际的索引在下面几个方面有一些不同:

每页的入口数量,在实际的索引中要比上面图中的多很多,每一层的页也要比图中的多很多。尤其是叶子层,在实际中会比图中多很多。

在实际的索引中,页上的入口是无序的。页入口的偏移指针,提供入口的顺序访问。(关于偏移指针可以查看第四级,页和分区中的介绍。)

索引的深度

根页的位置和索引的其他信息存储在一张系统表中。当SQL Server需要访问给定索引键值的索引入口的时候,它就用自己的方式从根页开始,访问每一层的每一页,直到包含索引键入口的叶子层。在我们十亿行表的例子中,SQL Server访问到需要的叶子层入口只需要读取5页;在上图的例子中,只需要读取3页就可以了。在聚集索引中,叶子层的入口就是实际的数据行,在非聚集索引中,入口可能是聚集索引的键,也可能是RID(Row ID)。

层的数目,也叫做深度,AdventureWorks数据库中,没有深度超过3的索引。数据库中如果有很大的表,或者索引键的列很多,深度有可能会超过6或者更深。

sys.dm_db_index_physical_stats函数给我们提供了一些索引的信息,包括索引的类型,深度,和大小;是一个表值函数,可以执行查询。下面的例子就是查看SalesOrderDetail表的索引信息。

SELECT OBJECT_NAME(P.OBJECT_ID) AS ‘Table‘
     , I.name AS ‘Index‘
     , P.index_id AS ‘IndexID‘
     , P.index_type_desc
     , P.index_depth
     , P.page_count
  FROM sys.dm_db_index_physical_stats (DB_ID(),
                                       OBJECT_ID(‘Sales.SalesOrderDetail‘),
                                       NULL, NULL, NULL) P
  JOIN sys.indexes I ON I.OBJECT_ID = P.OBJECT_ID
                    AND I.index_id = P.index_id; 

结果显示如下。

下面的代码会显示表的指定的索引的信息,SalesOrderDetail表的uniqueidentifier列的非聚集索引,结果中的一行就是索引的一层。

SELECT OBJECT_NAME(P.OBJECT_ID) AS ‘Table‘
     , I.name AS ‘Index‘
     , P.index_id AS ‘IndexID‘
     , P.index_type_desc
     , P.index_level
     , P.page_count
  FROM sys.dm_db_index_physical_stats (DB_ID(), OBJECT_ID(‘Sales.SalesOrderDetail‘), 2, NULL, ‘DETAILED‘) P
  JOIN sys.indexes I ON I.OBJECT_ID = P.OBJECT_ID
                    AND I.index_id = P.index_id;  

结果如下。

从上图的结果中,我们可以看出:

  • 索引的叶子层有408页。
  • 唯一的中间层只有2页。
  • 根层只有1页。

非叶子层的大小通常是叶子层的十分之一或者百分之二,这依赖于查询键包含哪些列,标签的大小,是否有包含列。换句话说,索引可能很大,也可能很小。

请记住,包含列只在非聚集索引中可用,他们只出现在叶子层的入口。在高层的入口中会忽略他们,这就是他们不增加非叶子层大小的原因。

因为聚集索引的叶子层就是表的数据行,在聚集索引中只有非叶子层额外的信息,需要额外的空间。无论是否创建索引,数据行都是存在的。因此,创建聚集索引的时候可能会花费一些时间,消耗一些资源,但是在创建完成之后,只需要很小的数据空间。

结论

索引的结构使得SQL Server可以快速的访问索引的入口。一旦发现入口,SQL Server就可以:

  • 访问入口的数据行。
  • 正向或者反向访问索引。

索引的树形结构已经使用了很长一段时间,甚至比关系数据库还要久远,随着时间它已经被证明很有用。

时间: 2024-10-01 12:55:16

SQL Server索引进阶:第十级,索引内部结构的相关文章

《Pro SQL Server Internals》翻译之索引

本文选自<Pro SQL Server Internals> 作者: Dmitri Korotkevitch 出版社: Apress 出版年: 2016-12-29 页数: 804 作者简介:Dmitri Korotkevitchis是微软SQL Server MVP和微软认证大师.作为应用程序和数据库开发人员.数据库管理员和数据库架构师,他具有多年使用SQL Server的经验.他专门从事OLTP系统在高负载下的设计.开发和性能调优.Dmitri经常在各种Microsoft和SQL PASS

SQL Server 2016 —— 聚集列存储索引的功能增强

作者 Jonathan Allen,译者         邵思华         发布于     2015年6月14日 聚集列存储索引(CC Index)是SQL Server 2014中两大最引人瞩目的特性之一,设计为用于超过1千万条记录的数据表.使用者无需明确的指定索引,也能够保证分析式查询的优良性能. 但2014版本中的这一特性存在着一个缺陷,即使用者无法指定索引.虽然CC索引比起传统表的表扫描要快得多,但它还是及不上经手动调整的覆盖索引.因此,为了同时支持这两种模式,开发者不得不创建两张

SQL Server 查询性能优化——创建索引原则(一)

索引是什么?索引是提高查询性能的一个重要工具,索引就是把查询语句所需要的少量数据添加到索引分页中,这样访问数据时只要访问少数索引的分页就可以.但是索引对于提高查询性能也不是万能的,也不是建立越多的索引就越好.索引建少了,用WHERE子句找数据效率低,不利于查找数据.索引建多了,不利于新增.修改和删除等操作,因为做这些操作时,SQL SERVER除了要更新数据表本身,还要连带地立即更新所有的相关索引,而且过多的索引也会浪费硬盘空间.因此要建得恰到好处,这就需要经验了. 一:索引的基本目的 索引的基

MS SQL Server:分区表、分区索引 详解

1. 分区表简介使用分区表的主要目的,是为了改善大型表以及具有各种访问模式的表的可伸缩性和可管理性. ?        大型表:数据量巨大的表.?        访问模式:因目的不同,需访问的不同的数据行集,每种目的的访问可以称之为一种访问模式. 分区一方面可以将数据分为更小.更易管理的部分,为提高性能起到一定的作用:另一方面,对于如果具有多个CPU的系统,分区可以是对表的操作通过并行的方式进行,这对于提升性能是非常有帮助的. 注意:只能在 SQL Server Enterprise Editi

SQL Server数据库性能优化之索引篇【转】

http://www.blogjava.net/allen-zhe/archive/2010/07/23/326966.html 性能优化之索引篇 近期项目需要, 做了一段时间的SQL Server性能优化,遇到了一些问题,也积累了一些经验,现总结一下,与君共享.SQL Server性能优化涉及到许多方面,如良好的系统和数据库设计,优质的SQL编写,合适的数据表索引设计,甚至各种硬件因素:网络性能.服务器的性能. 操作系统的性能,甚至网卡.交换机等.这篇文章主要讲到如何改善索引,还将有另一篇讨论

SQL Server数据库性能优化之索引篇

SQL Server性能优化涉及到许多方面,如良好的系统和数据库设计,优质的SQL编写,合适的数据表索引设计,甚至各种硬件因素:网络性能.服务器的性能.操作系统的性能,甚至网卡.交换机等.这篇文章主要讲到如何改善索引 当根据索引码的值搜索数据时,索引提供了对数据的快速访问.事实上,没有索引,数据库也能根据SELECT语句成功地检索到结果,但随着表变得越来越大,使用“适当”的索引的效果就越来越明显.索引有助于提高检索性能,但过多或不当的索引也会导致系统低效.因为用户在表中每加进一个索引,数据库就要

SQL Server数据库进阶之表分区实战演练

一.课程介绍 1.1.需求背景 假设,你有一个销售记录表,记录着每个销售情况,那么你就可以把这个销售记录表按时间分成几个小表,例如说5个小表吧.2009年以前的记录使用一个表,2010年的记录使用一个表,2011年的记录使用一个表,2012年的记录使用一个表,2012年以后的记录使用一个表.那么,你想查询哪个年份的记录,就可以去相对应的表里查询,由于每个表中的记录数少了,查询起来时间自然也会减少.但将一个大表分成几个小表的处理方式,会给程序员增加编程上的难度.以添加记录为例,以上5个表是独立的5

SQL Server 内存优化表的索引设计

测试的版本:SQL Server 2017 内存优化表上可以创建三种类型的索引,分别是:Hash Index.内存优化非聚集(NONCLUSTERED)索引和聚集(CLUSTERED)列存储索引. 本文着重分享非聚集索引和哈希索引,这两个索引适用的场景是: 非聚集索引   如果查询中包含order by子句.或者包含 where index_column > value等范围扫描操作 ,推荐使用非聚集索引. 哈希索引       如果查询中包含点查找(point lookup),例如 where

SQL Server -&gt;&gt; Online Index Rebuilding(联机索引重建)

SQL Server的Enterprise Edition是支持联机索引重建的.那么联机索引重建是怎么工作的以及对我们的查询有什么影响呢? 既然是联机,SQL Server保持了现有索引对于用户的可用,也就意味着它还不会去修改现有索引以及它相关联的统计数据.那么可以猜想下它的做法其实和SQL Server下Switch Partition就有相似的做法了 -- 最小化数据离线时间.它应该是先生成索引和统计数据,这个时候新的索引和统计数据肯定都分配好了object_id,内部肯定是已经可见了,只是

SQL Server 查询性能优化——创建索引原则(二)

三:索引的建立原则 一般来说,建立索引要看数据使用的场景,换句话来说哪些访问数据的SQL语句是常用的,而这些语句是否因为缺少索引(也有可能是索引过多)变的效率低下.但绝不是所有的SQL语句都要建立索引,如果所有的SQL语句都建立索引,那么可能导致建立过多的索引. 我碰到过每秒钟新增记录超过千条的案例,虽然该数据表仅有聚集索引,但因为已存在的键值字段的值和新增数据键值字段的值并不是按顺序递增,每次新增记录时,肯定造成整体数据行的重新排列.在移掉聚集索引后,性能约提升20%.也曾经碰到过一个数据表上