🌝

如何设计 MySQL 索引(一):基础概念

Posted at — Jan 14, 2023

1. B+ Tree

MySQL中的InnoDB引擎使用B+Tree结构来存储索引,可以尽量减少数据查询时磁盘IO次数,同时树的高度直接影响了查询的性能,一般树的高度维持在 3~4 层1。B+Tree中,只有叶子节点存储数据。

从【根节点】到每个【叶子节点】的距离是相等的,也就是访问任何一个叶子节点,需要的 IO 是一样的,即索引树的高度 + 1 次 IO 操作。

2. 数据页

当某个索引很大,大到达几十个G,远远超过了内存的容量时,MySQL不可能把整个索引全部加载到内存。MySQL 的做法是从磁盘以【页】为单位加载数据到内存。MySQL通过将索引拆分成页,一页为其最小数据单元,每次读取数据时,加载其中某几页数据。写也是按页,刷新一页的数据到磁盘。【页】是InnoDB存储引擎管理数据库的最小磁盘单位,一个页的大小一般是16KB23

3. 主键索引、辅助索引

主键索引:数据按照主键顺序存储(逻辑上是连续,物理上不连续),存储整行数据。如果没有显示的指定主键,MySQL 会将所有的列组合起来构造一个row_id作为primary key。由于叶子节点包含了完整的数据记录,所以主键索引又叫聚簇索引(clustered index);

辅助索引:也称为二级索引(secondary index),索引中除了存储索引列外,还存储了主键id,对于user_name的索引idx_user_name(user_name)而言,其实等价于idx_user_name(user_name, id),MySQL会自动在辅助索引的最后添加上主键id,不需要要用户自己显式写出来

4. 页分裂

如果自定义主键 id 不是自增的(乱序),有发生页分裂的可能。考虑以下情况,如果页内存储的键值为 [20, 21, 22, 23, 24, 25, 26],如果新增键值 27 时页满需要发生分裂。

创建一个新页,确定在 23 的位置分裂,移动记录至新页。

5. 页合并

当你删了一行记录时,实际上记录并没有被物理删除,记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。[&]

当页中删除的记录达到MERGE_THRESHOLD(默认页体积的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。刚好下一页使用了不到一半的空间,前一页又有足够的删除数量,它们能够进行合并。

合并操作使得前一页数据保留,并且容纳来下一页的数据。原来下一页变成一个空页,可以接纳新数据:

6. 文件排序

在 MySQL 中的 ORDER BY 有两种排序实现方式:利用有序索引排序(最优,using index)和文件排序(using filesort)。

只有当ORDER BY中所有的列必须包含在相同的索引,并且索引的顺序和order by子句中的顺序完全一致,并且所有列的排序方向(升序或者降序)一样才有,(混合使用ASC模式和DESC模式则不使用索引)4

当排序数据量不超过 sort_buffer_size 系统变量所设置的容量时,MySQL 将会在内存使用快速排序算法进行排序(内部排序);当排序数据量超过 sort_buffer_size 容量时,MySQL 将会借助临时磁盘文件使用归并排序算法进行排序(外部排序)在进行真正排序时,MySQL 又会根据数据单行长度是否超过 max_length_for_sort_data 而决定使用主键排序还是全字段排序,优先选择全字段排序,以减少回表次数5

7. 联合索引

指包含多个数据列的索引,与之概念相对的是单列索引,仅包含一个数据列。

8. 最左前缀原则

最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到索引。

组合索引的最左前缀匹配原则:使用组合索引查询时,mysql会一直向右匹配直至遇到范围查询(>、<、between、like)就停止匹配。可以说创建的idx_abc(a,b,c)索引,相当于创建了(a)、(a,b)(a,b,c)三个索引。

如 User 表有联合索引 (name, city),以下 SQL 语句:

1
2
3
select * from user where name=xx and city=xx ; -- 可以命中索引,用到(name, city)
select * from user where name=xx ; -- 可以命中索引,用到(name)
select * from user where city=xx ; -- 无法命中索引    

9. 索引覆盖

覆盖索引是一种优化手段。在使用联合索引时,拿到主键后,还需要再根据主键回表再获取到数据。如果我只需要abc字段的,那是不是意味着我们查询到组合索引(a, b, c)的叶子节点就可以直接返回了,而不需要回表。这种情况就是覆盖索引。

参考资料