1. BTree索引
- B-Tree即B树,Balance Tree,平衡树,它的高度远小于平衡二叉树的高度。
- 2-3树是最简单的B树结构。
- B树的阶:节点的最多子节点个数。比如2-3树的阶是3,2-3-4树的阶是4。
1.1 初始化介绍
一颗b树,浅蓝色的块我们称之为一个磁盘块(innodb默认16kb一个磁盘块),可以看到每个磁盘块包含几个数据项(深蓝色所示)、指向关键字具体信息的指针(红色)和指向其他磁盘块的指针(黄色所示)如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。
1.2 查找过程
如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。
1.3 真实的情况
3层的b树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
2. B+Tree索引
2.1 数据结构
2.2 B+Tree与B-Tree 的区别
B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;
B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。树的高度会更矮胖,IO次数也会更少。
在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看B-树的性能好像要比B+树好,而在实际应用中却是B+树的性能要好些。因为B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比B-树多,树高比B-树小,这样带来的好处是减少磁盘访问次数。尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些,而且B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用B+树的缘故。
思考:为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引?
① B+树的磁盘读写代价更低
B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
② B+树的查询效率更加稳定
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
2.3 数据页大小
查看mysql文件页大小(16K):
SHOW GLOBAL STATUS LIKE '%page_size%'
为什么mysql页文件默认16K?
假设我们一行数据大小为1K,那么一页就能存16条数据,也就是一个叶子节点能存16条数据;再看非叶子节点,假设主键ID为bigint类型,那么长度为8B,指针大小在Innodb源码中为6B,一共就是14B,那么一页里就可以存储16K/14B=1170个(主键+指针)
一颗高度为2的B+树能存储的数据为:1170*16=18720条
一颗高度为3的B+树能存储的数据为:1170*1170*16=21902400(千万级)
3. 聚簇索引与非聚簇索引
3.1 聚簇索引
- 将数据存储与索引放到了一块,找到索引也就找到了数据。
- 页内的记录是按照主键的大小顺序排成一个单向链表 。
- 页和页之间也是根据页中记录的主键的大小顺序排成一个双向链表 。
- 非叶子节点存储的是记录的主键+页号。叶子节点存储的是完整的用户记录。
3.2 非聚簇索引
将数据存储与索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因。
3.3 澄清一个概念
innodb中,非聚簇索引又称辅助索引,辅助索引访问数据总是需要二次查找。辅助索引叶子节点存储的不再是行的物理位置,而是主键值。
InnoDB使用聚簇索引,将主键组织到一棵B+树中,而行数据就储存在叶子节点上,若使用"where id = 14"这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。
若对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主索引B+树种再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据。(重点在于通过其他键需要建立辅助索引)
3.4 聚簇索引的好处
-
由于行数据和叶子节点存储在一起,同一页中会有多条行数据,访问同一数据页不同行记录时,已经把页加载到了Buffer中,再次访问的时候,会在内存中完成访问,不必访问磁盘。这样主键和行数据是一起被载入内存的,找到叶子节点就可以立刻将行数据返回了,如果按照主键Id来组织数据,获得数据更快。
-
聚簇索引适合用在排序的场合,非聚簇索引不适合
-
取出一定范围数据的时候,使用聚簇索引
-
二级索引需要两次索引查找,而不是一次才能取到数据,因为存储引擎第一次需要通过二级索引找到索引的叶子节点,从而找到数据的主键,然后在聚簇索引中用主键再次查找索引,再找到数据
3.5 聚簇索引的限制
-
对于mysql数据库目前只有innodb数据引擎支持聚簇索引,而Myisam并不支持聚簇索引。
-
由于数据物理存储排序方式只能有一种,所以每个Mysql的表只能有一个聚簇索引。
-
一般情况下就是该表的主键。
-
如果没有primary key,会以(not null unique key)非空的唯一索引保存数据
-
内部自己生成一个字段保存数据
-
-
为了充分利用聚簇索引的聚簇的特性,所以innodb表的主键列尽量选用有序非空的字段,而不建议用无序的id,比如uuid这种。
4. 回表
通过非聚簇索引查找到主键值之后仍然需要到聚簇索引中再查询一遍,这个过程称为回表。
为什么我们还需要一次回表操作呢?直接把完整的用户记录放到叶子节点不OK吗?
如果把完整的用户记录放到叶子节点是可以不用回表。但是太占地方了,相当于每建立一棵B+树都需要把所有的用户记录再都拷贝一遍,这就有点太浪费存储空间了。
5. 联合索引
基于多个字段创建的索引就是联合索引,也称为复合索引,比如我们创建索引create index idx on table(a,b,c) 我们称在字段a,b,c上创建了一个联合索引。同时以这三个列的大小作为排序规则。
-
记录先按照a列排序
-
a列值相同时使用b列排序
-
b列值相同时使用c列排序
然后将排好序的abc三列的值组织到非聚簇索引索引结构中。
联合索引结构: