专栏地址:
文章目录
1. 常见的索引模型
索引的目的是为了提高数据的查询检索效率,常见的索引模型有:哈希表、有序数组、搜索树。
哈希表
哈希表以一种 key-value 的形式存储数据。由于存储结构不是有序的,所以哈希索引不适合做范围查询、索引完成排序等,但其在等值查询的场景下具有绝对优势。
在Redis的ZSET中,在哈希表的基础上增加了跳表结构,使其既可以支持哈希访问,也可以做快速的范围查询。
有序数组
有序数组在等值查询和范围查询的场景下性能都很优秀,时间复杂度达到了O(lgN)。但是其数据增加和删除的成本太高,只实用于静态存储引擎。
搜索树
二叉树的特点是:每个父节点,比其左儿子大,比其右儿子小。其查询的时间复杂度是O(lgN)。为了维持O(lgN)的查询复杂度,就得保持这棵树是平衡二叉树,所以更新操作也是O(lgN)。
搜索树可以有多叉,子节点之间保持从左到右的有序递增。
二叉树的搜索效率最高,但在数据库索引很少使用,原因是索引不仅存在于内存,还要写到磁盘上。为了减少IO次数,就必须让查询过程中访问尽量少的数据块。
2. B+树索引
B+Tree是为了硬盘或者其它外存设计的平衡多路查找树,与平衡二叉查找树相比,B+Tree是多叉树,每个节点保存多个元素,高度更低,可有效减少IO次数。
MyISAM引擎和InnoDB都是用B+Tree作为索引结构,不同的是,MyISAM索引和数据是分离的,B+树中保存着数据的地址;InnoDB中,表数据就是按照B+Tree组织,叶子节点存放着数据记录,这种表结构称之为索引组织表。在数据库中,B+Tree的高度一般在2-4层,查询操作只需2-4次IO。
2.1 B-Tree–有序数组+平衡多叉树
- 一个m阶的B+Tree每个节点最多m个子节点以及m-1个键。(PS:m-1个键划分出m个区间,子节点不在出现父结点已有的元素,每个节点元素只出现一次)
- 除了根节点和叶子节点外,每个节点至少有m/2个子节点。
- 每个节点的元素从小到大排序。
2.2 B+Tree—有序数组+链表+平衡多叉树
- 节点元素与其子节点数相同。(m阶,m个元素,m个子节点)
- 父节点保存的是每个子树的最小值。
- 由2可知,叶子节点保存所有元素。
- 叶子节点通过指针(双向链表)相连
2.3 对比
B-Tree中每个元素只出现一遍,而B+Tree中所有元素都在叶子节点出现。所以B-Tree需要在每个节点中保存数据,而B+Tree只需在叶子节点保存数据即可,故B+Tree的内部结构更小,阶数可以更大。B+Tree除了随机检索还支持顺序检索。B+Tree的查询效率更加稳定,任何元素的查找都需要从根节点到叶节点,路径长度一致。
2.4 B+树的操作
B+树为了保证索引的有序性,在索引发生变更的时候需要进行必要的维护。
查找
类似二叉树查找,在节点内部使用二分查找。
增加
索引的随机插入,如果当前数据页已经满了,则会导致页分裂——申请一个新的数据页,然后移动部分数据到新的数据页。页分裂会到了性能损耗(涉及到磁盘操作),也降低了页的使用率。InnoDB为了减少页分裂,在兄弟节点没有满的情况下,会进行旋转操作,将记录转移到兄弟节点上。
追加不会导致页分裂,但和随机插入一样,将节点插入父节点会使B+树向上生长——父节点数量也都超出,B+树变高。

删除
相邻两个页由于删除导致利用率较低后,会对数据页进行合并。InnoDB使用填充因子来控制树的删除变化。
2.5 InnoDB为什么选择B+树
B+Tree是为需要外存随机访问设计的平衡多路查找树。
与二叉树相比,其每个节点可以保存的元素更多,每次IO可以读取到的元素更多;树的高度也因而更低,所需的IO次数更少。
与B-Tree相比,B+Tree只在叶子节点保存数据,内部结构更加紧凑,阶数更大,因而IO次数也更少;B+树查询效率更加稳定,还支持顺序检索。
但是,对于离根节点较近的子节点,B-Tree由于子节点同时包含了Key以及Value(数据地址),因此无需访问至叶子节点,速度更快。
3. 索引组织表
InnoDB中的表数据是一颗按照主键构建的B+树,这种存储方式称之为索引组织表,主键索引树的叶子节点保存了完整的数据记录。

3.1 聚簇索引
聚簇索引(clustered index)定义了表中数据的存储结构,在InnoDB中主键索引也被称为聚簇索引。
InnoDB要求表必须有主键(MyISAM可以没有)。如果没有,InnoDB会自动选择一个可以唯一标识记录的列(非空唯一键)作为主键;若不存在,则生成一个6字节的隐含字段rowid作为主键。
InnoDB为什么必须有主键:
- 存储结构为索引组织表
- 二级索引回表需要借助主键查询到记录
3.2 二级索引(辅助索引)
非主键索引也称为二级索引,其叶子节点保存的是二级索引及其对应的聚簇索引,即主键。InnoDB会把主键字段放到索引定义字段后面,当然同时也会去重,若主键的部分列(复合索引)已经包含二级索引中,则不会再重复记录该列。
利用二级索引进行查询时,相较于利用主键查询,需要一次额外的回表操作——得到主键之后,再到聚簇索引树中进行查找。
利用覆盖索引可以减少回表次数,同时InnoDB也引入了索引下推和MRR来降低回表次数和开销,前者将部分查询条件(索引和主键包含的列)下推到引擎层,InnoDB在回表前会先进行过滤;后者在回表前先对主键进行排序,以降低随机IO(回表还是一行一行的进行主键查询)。
3.3 一颗B+树可以存储多少数据
InnoDB采用了B+树作为存储的数据结构,这是一种为外存随机IO优化的平衡多路搜索树,是一颗“N叉“树。N的大小取决于数据页和索引的大小。
在InnoDB中,数据页的地址指针为6个字节,在未开启压缩页的情况下,数据页的大小为16k。假设主键索引采用8个字节的bigint类型,那么每个索引大概占据14字节。在B+树的非叶子节点中,忽略数据页的固定开销,每个数据页大概可以存放16384➗14=1170个索引,估算为1024。
假设每条记录的大小为1k,那么在叶子结点的每个数据页可以存放16条记录。
一个高度为3的B+树可以存放:1024102416=1677216条记录,大小为1024102416KB=16GB。
所以在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储。在查找数据时一次⻚的查找代表一次IO,所以通过主键索引查询通常只需要1-3次IO操作即可查找到数据。考虑到树根的数据页一般位于内存,那么IO次数将更少。
4. 哈希索引
见 InnoDB整体架构与关键技术 中的自适应哈希索引AHI。
5. 全文检索
全文检索(Full-Text Search)利用倒排索引(Inverted Index)来实现。倒排索引可以根据单词快速获取包含这个单词的文档矩阵,其常见格式是:key = 单词, value = 文档ID + 单词位置。
InnoDB将倒排索引存储于辅助表中,且为了提高并行度,使用了多张辅助表。
MySQL使用MATCH (‘Col Name’) AGAINST (‘Search’)来进行全文检索,结果集依据相关性降序排列。相关度依据单词在文档中是否出现、出现的次数、多少其他文档包含该单词等因素计算。