目录

什么是索引?

索引的优点和缺点

数据页

什么是数据页

页与页之间的连接结构(双链表)

 单页查找

多页查找

聚簇索引,二级索引(非聚簇索引、辅助索引)

聚簇索引

非聚簇索引 

  聚簇索引与非聚簇索引比较小结

Innodb B+树索引的特性

MyIsam索引的特性

Innodb索引与MyIsam索引比较 

再次理解索引的代价(缺陷)

除B+树索引,其他的索引结构(Hash,AVL,B,B+对比)

Hash结构

平衡二叉树(AVL) 

B-  Tree(多路平衡查找树)

B+树 

一些索引树思考题

扩展:R树 


什么是索引?

用最简单的话来说:索引就像是书的目录,可以让我们快速定位查询到数据!

索引与存储引擎有关,Innodb的索引底层用的是B+树。

索引的优点和缺点

(1)类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本 ,这也是创建索引最主 要的原因。

(2)通过创建唯一索引,可以保证数据库表中每一行数据的唯一性

(3)在实现数据的 参考完整性方面,可以 加速表和表之间的连接 。换句话说,对于有依赖关系的子表和父表联合查询时, 可以提高查询速度。

(4)在使用分组和排序子句进行数据查询时,可以显著减少查询中分组和排序的时间,降低了CPU的消耗。

增加索引也有许多不利的方面,主要表现在如下几个方面:

(1)创建索引和维护索引要 耗费时间 ,并且随着数据量的增加,所耗费的时间也会增加。

(2)索引需要占磁盘空间 ,除了数据表占数据空间之 外,每一个索引还要占一定的物理空间, 存储在磁盘上 ,如果有大量的索引,索引文件就可能比数据文件更快达到最大文件尺寸。

(3)虽然索引大大提高了查询速度,同时却会降低更新表的速度 。当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。

数据页

什么是数据页

在操作系统中,我们知道为了跟磁盘交互,内存也是分页的,一页大小4KB。同样的在MySQL中为了提高吞吐率,数据也是分页的,不过MySQL的数据页大小是16KB。(确切的说是InnoDB数据页大小16KB)。详细学习可以参考官网。数据页是Mysql的基本存储单位。

页与页之间的连接结构(双链表)

 单页查找

如果数据比较少,就存储在单页中

 单链表是什么意思?我们在数据页里面存储了一条条记录,它里面的记录也有一定的格式(行格式)。因为数据库中快存储很多条记录,所以记录之间并不是物理连续的,否则插入一条数据会全盘移动,因此它们是用单链表连接的!

多页查找

聚簇索引,二级索引(非聚簇索引、辅助索引)

索引按照物理实现方式,索引可以分为 2 种:聚簇(聚集)和非聚簇(非聚集)索引。我们也把非聚集 索引称为二级索引或者辅助索引。

聚簇索引

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式(所有的用户记录都存储在了叶子节点),也就是所谓的索引即数据,数据即索引

术语”聚簇”表示数据行和相邻的键值聚簇的存储在一起。|

 我们把具有这两种特性的B+树称为聚簇索引,所有完整的用户记录都存放在这个聚簇索引的叶子节点处。这种聚簇索引并不需要我们在MysQL语句中显式的使用INDEX语句去创建,InnoDB存储引擎会自动的为我们创建聚簇索引。

 

非聚簇索引 

上边介绍的聚簇索引只能在搜索条件是主键值时才能发挥作用,因为B+树中的数据都是按照主键进行排序的。
那如果我们想以别的列作为搜索条件该怎么办呢” />

 举个例子:

  1. InnoDB使用的是聚簇索引,将主键组织到一棵B+树中,而行数据就储存在叶子节点上,若使用“where id = 14”这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。
  2. 若对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主索引B+树种再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据。(重点在于通过其他键需要建立辅助索引)

概念:回表 我们根据这个以c2列大小排序的B+树只能确定我们要查找记录的主键值,所以如果我们想根 据c2列的值查找到完整的用户记录的话,仍然需要到 聚簇索引 中再查一遍,这个过程称为 回表 。也就 是根据c2列的值查询一条完整的用户记录需要使用到 2 棵B+树!

问题:为什么我们还需要一次 回表 操作呢?直接把完整的用户记录放到叶子节点不OK吗?

概念:回表 我们根据这个以c2列大小排序的B+树只能确定我们要查找记录的主键值,所以如果我们想根 据c2列的值查找到完整的用户记录的话,仍然需要到 聚簇索引 中再查一遍,这个过程称为 回表 。也就 是根据c2列的值查询一条完整的用户记录需要使用到 2 棵B+树!

问题:为什么我们还需要一次 回表 操作呢?直接把完整的用户记录放到叶子节点不OK吗? 

  聚簇索引与非聚簇索引比较小结

 聚簇索引和非聚簇索引的关系 – 小学生很小 – 博客园

Innodb B+树索引的特性

1.根页面位置万年不动

我们前边介绍B+树索引的时候,为了大家理解上的方便,先把存储用户记录的叶子节点都画出来,然后接着画存储目录项记录的内节点,实际上B+树的形成过程是这样的:
  (1)     每当为某个表创建一个B+树索引(聚簇索引不是人为创建的,默认就有)的时候,都会为这个索引创建一个根节点页面。最开始表中没有数据的时候,每个B+树索引对应的根节点中既没有用户记录,也没有目录项记录。
(2)  随后向表中插入用户记录时,先把用户记录存储到这个根节点中
(3)  当根节点中的可用空间用完时继续插入记录,此时会将根节点中的所有记录复制到一个新分配的页,比如页a中,然后对这个新页进行页分裂的操作,得到另一个新页,比如页b。这时新插入的记录根据键值(也就是聚簇索引中的主键值,二级索引中对应的索引列的值)的大小就会被分配到页a或者b中,而根节点便升级为存储目录项记录的页。

不管表里的数据怎么变,B+树的根节点始终都不会改变。InnoDB会将索引树的根节点的页号存储在某个地方便于管理,根节点固定不变,也便于InnoDB将根节点缓存起来,减少一次磁盘IO

2、目录项记录的唯一性 

InnoDB允许创建「非唯一索引」,这意味着二级索引B+树中可能存在大量索引值相同的目录项,如果连续的页索引值都相同,那意味着内节点中会有两条索引值相同的目录项记录。此时,如果再插入一条索引值相同的记录,那到底该插入到哪一个页里呢?这种情况下InnoDB也不知道该怎么办了。所以,对于非唯一索引,B+树中的目录项记录除了记录索引列+页号外,还会额外存储主键id,确保目录项记录是唯一且有序的,索引值相同则按照id排序。

3、一个页面最少存储2条记录
B+树快速检索的本质是因为它每经过一个节点都可以快速的过滤掉大量的无效节点,大大的减少了索引页的加载次数。理论上来说,单个索引页包含的记录数越多,查询的效率就越高。我们上面计算过,一个页面可以包含约80条200字节的用户记录、约960条目录项记录,这样即使上亿条记录树的高度也不会超过4。一旦页内包含的记录数少了,B+树就会变高,查询效率会指数级下降。所以,为了防止极端情况出现,InnoDB规定,单个索引页内最少包含2条记录。
 

MyIsam索引的特性

即使多个存储引擎支持同一种类型的索引,但是他们的实现原理也是不同的。Innodb和MyISAM默认的索 引是Btree索引;而Memory默认的索引是Hash索引。

MyIsam索引和数据是分开存放的,因此MyIsam没有聚簇索引,叶子节点的data域存放的是 数据记录的地址

Innodb索引与MyIsam索引比较 

小结:
了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助。比如:

举例1:   知道了InnoDB的索引实现后,就很容易明白为什么不建议使用过长的字段作为主键,因为所有二级索引都引用主键索引,过长的主键索引会令二级索引变得过大

举例2:   用非单调的字段作为主键在InnoDB中不是个好主意,因为InnoDB数据文件本身是一棵B+Tree,非单调的主键会造成在插入新记录时,数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。 

再次理解索引的代价(缺陷)

所以说,为什么我们删除数据的时候也是都进行逻辑删除~也能有了理解了吧~ 

除B+树索引,其他的索引结构(Hash,AVL,B,B+对比)

如果不使用如何数据结构去搜索索引,全局遍历,这是极其不现实的。因此我们选择合适的数据结构与算法,主要是根据它的时间复杂度。

从MysQL的角度讲,不得不考虑一个现实问题就是磁盘IO。如果我们能让索引的数据结构尽量减少硬盘的I/o操作,所消耗的时间也就越小。可以说,磁盘的 I/0操作次数对索引的使用效率至关重要
查找都是索引操作,一般来说索引非常大,尤其是关系型数据库,当数据量比较大的时候,索引的大小有可能几个G甚至更多,为了减少索引在内存的占用,数据库索引是存储在外部磁盘上的。当我们利用索引查询的时候,不可能把整个索引全部加载到内存,只能逐一加载,那么MySQL衡量查询效率的标准就是磁盘IO次数。

加速查找速度的数据结构,常见的有两类:
(1)树,例如平衡二叉搜索树,查询/插入/修改/删除的平均时间复杂度都是0(log2N);

⑵哈希,例如HashMap,查询/插入/修改/删除的平均时间复杂度都是0(1); 

Hash结构

Hash本身是一个函数,又被称为散列函数,它可以帮助我们大幅提升检索数据的效率。
Hash算法是通过某种确定性的算法(比如MD5、SHA1、SHA2、SHA3)将输入转变为输出。相同的输入永远可以得到相同的输出,假设输入内容有微小偏差,在输出中通常会有不同的结果。
举例:如果你想要验证两个文件是否相同,那么你不需要把两份文件直接拿来比对,只需要让对方把Hash函数计算得到的结果告诉你即可,然后在本地同样对文件进行Hash 函数的运算,最后通过比较这两个Hash 函数的结果哈希值是否相同,就可以知道这两个文件是否相同。

哈希底层jdk1.8之后采用数据+链表+红黑树,源码理解可以去看我的集合文章

JDK1.8之前:哈希表=数组+链表+(哈希算法)

JDK1.8之后(包括):哈希表=数组+链表+红黑树+(哈希算法)

       当链表长度超过阈值(8)时,将链表转换为红黑树,这样会大大减小查找时间。

–>哈希表由数组和链表组成
–>当集合添加元素的时候会生成一个哈希值,哈希值是根据地址或字符串或数字算出来的int类型数值
–>根据元素的哈希值跟数组的长度求余算出应存入位置
–>判断数组中当前位置是否为NULL,(1)如果为null直接存入 (2) 如果不为null,表示有元素,采用equals比较属性值①一样则不存 ②不一样,则存入数组,老元素挂在新元素下面形成链表
–>如果数组存满到16*0.75=12时,数组就会自动扩容为原来的两倍 

采用Hash进行检索效率非常高,基本上一次检索就可以找到数据,而B+树需要自顶向下依次查找,多次访问节点才能找到数据,中间需要多次l/o操作,从效率来说 Hash 比 B+树更快。 

Hash结构效率高,那为什么索引结构要设计成树型呢? 

 

平衡二叉树(AVL) 

首先说一下,平衡二叉树可以避免二叉树一种极端情况。

 所以我们直接用AVL控制它的深度,我们来直接说一下AVL。AVL左右子树绝对高度差不能超过1

 但是AVL也会有一种问题,就是n比较小,树的分叉M比较多时,就会变得“痩  高”形如平衡的极差的普通二叉树。所以我们需要把他改造成“矮 胖”  引入了B树

B-  Tree(多路平衡查找树)

一个 M 阶的 B 树(M>2)有以下的特性:

1. 根节点的儿子数的范围是 [2,M]。

2. 每个中间节点包含 k-1 个关键字和 k 个孩子,孩子的数量 = 关键字的数量 +1,k 的取值范围为 [ceil(M/2), M]。

3. 叶子节点包括 k-1 个关键字(叶子节点没有孩子),k 的取值范围为 [ceil(M/2), M]。

4. 假设中间节点节点的关键字为:Key[1], Key[2], …, Key[k-1],且关键字按照升序排序,即 Key[i]<Key[i+1]。此时 k-1 个关键字相当于划分了 k 个范围,也就是对应着 k 个指针,即为:P[1], P[2], …P[k],其中 P[1] 指向关键字小于 Key[1] 的子树,P[i] 指向关键字属于 (Key[i-1], Key[i]) 的子树,P[k] 指向关键字大于 Key[k-1] 的子树。

5. 所有叶子节点位于同一层。

上面那张图所表示的 B 树就是一棵 3 阶的 B 树。

我们可以看下磁盘块 2,里面的关键字为(8,12),它 有 3 个孩子 (3,5),(9,10) 和 (13,15),你能看到 (3,5) 小于 8,(9,10) 在 8 和 12 之间,而 (13,15) 大于 12,刚好符合刚才我们给出的特征。 然后我们来看下如何用 B 树进行查找。

假设我们想要 查找的关键字是 9 ,那么步骤可以分为以下几步: 1. 我们与根节点的关键字 (17,35)进行比较,9 小于 17 那么得到指针 P1; 2. 按照指针 P1 找到磁盘块   2,关键字为(8,12),因为 9 在 8 和 12 之间,所以我们得到指针 P2; 3. 按照指针 P2 找到磁盘块 6,关键字为(9,10),然后我们找到了关键字 9。 你能看出来在 B 树的搜索过程中,我们比较的次数并不少,但如果把数据读取出来然后在内存中进行比 较,这个时间就是可以忽略不计的。而读取磁盘块本身需要进行 I/O 操作,消耗的时间比在内存中进行 比较所需要的时间要多,是数据查找用时的重要因素。 B 树相比于平衡二叉树来说磁盘 I/O 操作要少 , 在数据查询中比平衡二叉树效率要高。所以 只要树的高度足够低,IO次数足够少,就可以提高查询性能 。

 

B+树 

B+树也是一种多路搜索树,基于B树做出了改进,主流的DBMS都支持B+树的索引方式,比如MySQL。相比于B-Tree,B+Tree适合文件索引系统

B+ 树和 B 树的差异:

1. 有 k 个孩子的节点就有 k 个关键字。也就是孩子数量 = 关键字数,而 B 树中,孩子数量 = 关键字数 +1。 索引 / 存储引擎 MyISAM InnoDB Memory R-Tree索引 支持 支持 不支持

2. 非叶子节点的关键字也会同时存在在子节点中,并且是在子节点中所有关键字的最大(或最 小)。

3. 非叶子节点仅用于索引,不保存数据记录,跟记录有关的信息都放在叶子节点中。而 B 树中, 非 叶子节点既保存索引,也保存数据记录 。

4. 所有关键字都在叶子节点出现,叶子节点构成一个有序链表,而且叶子节点本身按照关键字的大 小从小到大顺序链接。

B 树和 B+ 树都可以作为索引的数据结构,在 MySQL 中采用的是 B+ 树。 但B树和B+树各有自己的应用场景,不能说B+树完全比B树好,反之亦然。

其实看下来B树与B+树最大的区别就是非叶子节点是否保存数据,那B+树只有叶子节点保存数据有什么好处呢? 

首先,B+树查询效率更稳定。因为B+树每次只有访问到叶子节点才能找到对应的数据,而在B树中,非叶子节点也会存储数据,这样就会造成查询效率不稳定的情况,有时候访问到了非叶子节点就可以找到关键字,而有时需要访问到叶子节点才能找到关键字。(这也不算啥好处其实= =)
其次,B+树的查询效率更高。这是因为通常B+树比B树更矮胖(阶数更大,深度更低),查询所需要的磁盘I/o 也会更少。同样的磁盘页大小,B+树可以存储更多的节点关键字因此页大小默认就16KB,你存了数据就必然少存关键字。关键字少你分叉的数就少,深度就得变深。

不仅是对单个关键字的查询上,在查询范围上,B+树的效率也比B树高。这是因为所有关键字都出现在B+树的叶子节点中,叶子节点之间会有指针数据又是递增的,这使得我们范围查找可以通过指针连接查找。而在B树中则需要通过中序遍历才能完成查询范围的查找,效率要低很多(一个树遍历(树遍历还要回溯= =),一个链表遍历)

一些索引树思考题

 

至于B+树高度为什么一般2~4层,看下面这段话

 

 

 

扩展:R树