索引的常见模型
索引的出现就是为了提高数据查询的效率。 用于提高读写效率的数据结构有很多,常见的几种:
- 哈希表
- 有序数组
- 搜索树
哈希表
- 适合等值查询场景,类似Memcached以及其他NoSQL引擎
- 不适合做区间查询,哈希表结构不是有序的。
哈希表是一种以 键-值(key-value)存储数据的结构,哈希的思路简单,把值放在数组里,用一个哈希函数把key换算成一个确定的位置,然后把value放在数组的这个位置。 不可避免的,多个key经过哈希函数的计算,会出现同一个值的情况,处理这种情况的一种方法是拉出一个链表。
现在维护着一个身份证信息和姓名的表,需要根据身份证号查找对应的名字,对应的哈希索引的示意图如下所示:
图中,user2和user4根据身份证号算出来的值都是N,此时若要查询ID_card_n2对应的名字,步骤如下:先将ID_card_n2通过哈希函数算出N,然后按顺序遍历,找到user2。
哈希表结构不是有序的,所以做区间查询是很慢的。
哈希表这种结构适用于等值查询的场景,比如Memcached以及其他NoSQL引擎。
有序数组
- 适合等值查询(二分法)
- 适合范围查询
- 缺点:更新操作成本过高
有序数组在等值查询和范围查询场景中性能都很优秀。
假设身份证号无重复,这个数组就是按照身份证号递增顺序保存,如果要查询ID_card_n2对应的名字,用二分法可以快速得到,时间复杂度为O(log(N))。
如果要做范围查询,比如查询身份证号在[ID_card_X,ID_card_Y]区间的User,可以用二分法查找到ID_card_X(若不存在ID_card_X,则找到大于ID_card_X的第一个User),然后向右遍历,直到找到第一个大于ID_card_Y的身份证号,退出循环。
但是,更新操作时,必须挪动后面所有的记录,成本过高。
有序数组索引只适用于静态存储结构。
二叉搜索树
二叉搜索树的特点是:每个节点的左儿子小于父节点,父节点又小于右儿子。这样如果你要查 ID_card_n2 的话,按照图中的搜索顺序就是按照 UserA -> UserC -> UserF -> User2这个路径得到。这个时间复杂度是 O(log(N))。
当然为了维持 O(log(N)) 的查询复杂度,你就需要保持这棵树是平衡二叉树。为了做这个保证,更新的时间复杂度也是 O(log(N))。
InnoDB的索引模型
在InnoDB中,表都是根据主键顺序以索引的形式存放的, 这种存储方式的表称为索引组织表。 InnoDB使用了B+树的索引模型,所以数据都是存储在B+树中。
每一个索引在InnoDB中对应一颗B+树。
假设有如下表,表有3个字段id、k、name,其中ID加了主键索引,k加了普通索引。
mysql> create table T(
id int primary key,
k int not null,
name varchar(16),
index (k))engine=InnoDB;
表中 R1~R5 的 (ID,k) 值分别为 (100,1)、(200,2)、(300,3)、(500,5) 和 (600,6),两棵树的示例示意图如下:
上图,根据叶子节点的内容,索引分为主键索引和非主键索引。
- 主键索引(聚簇索引clustered index):叶子节点存的是整行数据。
- 非主键索引(二级索引secondary index):叶子节点内容是主键的值。
主键索引和普通索引的查询过程有什么区别?
- 如果语句是 select * from T where ID=500,即主键查询方式,则只需要搜索 ID 这棵B+ 树;
- 如果语句是 select * from T where k=5,即普通索引查询方式,则需要先搜索 k 索引树,得到 ID 的值为 500,再到 ID 索引树搜索一次。这个过程称为回表。
索引维护
B+ 树为了维护索引有序性,在插入新值的时候需要做必要的维护。以上面这个图为例,如果插入新的行 ID 值为 700,则只需要在 R5 的记录后面插入一个新记录。如果新插入的 ID值为 400,就相对麻烦了,需要逻辑上挪动后面的数据,空出位置。
页分裂
如果 R5 所在的数据页已经满了,根据 B+ 树的算法,这时候需要申请一个新的数据页,然后挪动部分数据过去。这个过程称为页分裂。在这种情况下,性能自然会受影响
页合并
有分裂就有合并。当相邻两个页由于删除了数据,利用率很低之后,会将数据页做合并。合并的过程,可以认为是分裂过程的逆过程。
自增主键的优点以及适用场景
自增主键:插入新记录的时候可以不指定 ID 的值,系统会获取当前 ID 最大值加 1 作为下一条记录的ID值。
【自增主键的优点】
- 优点1:避免了叶子节点的分裂:自增主键的插入数据模式,每次插入一条新记录,都是追加操作,不涉及挪动其他记录,也不会触发叶子节点的分裂。
- 优点2:节约普通索引占用空间,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。(虽然身份证号(即业务字段)也可以作为主键,但是占用的字节太大,普通索引的叶子节点是主键的值,会导致索引占用空间过大)
结论:从性能和存储空间方面考虑,自增主键往往是更合理的选择。
什么场景适合用业务字段做主键?
比如有的业务场景需求是这样:
- 1.只有一个索引
- 2.该索引必须是唯一索引
典型的KV场景,由于没有其他索引,无需考虑其他索引的叶子节点大小的问题。