索引的常见模型
实现索引的方式有很多种,这里先介绍三种常见结构:哈希表、有序数组和搜索树。
哈希表:只适用于只有等值查询的场景。
有序数组:在等值查询和范围查询场景中的性能都非常优秀,但是在更新数据的时候需要挪动大量记录。因此,只适用于静态存储引擎。
二叉搜索树:树层数可能很高,可能一个节点上的数据在一个物理数据块上,那么访问叶子节点的数据需要大量磁盘IO。
为了让一个查询尽量少进行磁盘IO,使用N叉树代替二叉树。
数据库技术发展至今,跳表、LSM树等结构也被用于引擎设计中。需要明白的是,数据库底层存储的核心就基于数据模型,因此每碰到一个新数据库,需要先关注它的数据模型,这样才能从理论上分析出这个数据库的适用场景。
InnoDB的索引模型
在InnoDB中,表都是根据主键顺序以索引的形式存放的。由于InnoDB使用了B+树索引模型,所以数据都是存储在B+树中的。
每一个索引在InnoDB里对应一棵B+树。
根据叶子节点的内容,索引类型分为主键索引和非主键索引:
-
主键索引的叶子结点存的是整行数据。在InnoDB里,主键索引也被称为聚簇索引。
-
非主键索引的叶子结点存的是主键的值。在InnoDB里,非主键索引也被称为二级索引。
对于非主键索引,假设查询select *
,那么需要先查询非主键的索引树,得到主键后再查询一次,这个过程称为回表。因此,应该尽量使用主键查询。
索引维护
B+树为了维护索引有序性,在插入新值时候需要做必要的维护。如果插入时所在的数据页已满,根据B+树的算法,这时候需要申请一个新的数据页,然后挪动部分数据过去,这个过程称为页分裂,会对性能造成较大影响。同时,页分裂还影响数据页的利用率,原本放在一个页的数据,分到两个页中,整体空间利用率降低大约50%。
页分裂的逆过程为页合并。当相邻两个页由于删除了数据,利用率很低之后,会将数据页做合并。
基于索引维护,讨论哪些场景应该使用自增主键,哪些情况不应该用。
当我们设置了自增主键,插入时可以不指定主键值,且插入符合递增插入,每次插入都是一次追加操作,不会挪动其他记录,也不会触发叶子结点的分裂。而以有业务逻辑的字段做主键,往往保证不了有序插入,这样就可能有页分裂。
除了考虑性能,从存储空间的角度来看,自增主键的字节数也往往较小,那么普通索引的叶子节点就会越小,占用的空间也会越小。
因此,从性能和存储空间角度,自增主键往往是更合理的选择。而有些业务场景,比如要求只有一个唯一索引,那么该索引列自然作为主键了。
参考资料:极客时间专栏《MySQL实战45讲》https://time.geekbang.org/column/intro/100020801?tab=catalog