从订单ID说起:揭秘MySQL索引结构 & 设计

引言

在订单业务中,我们要如何做到使用非自增主键ID,但是又保证一定的查询性能呢?一般来说,我们可以通过引入索引来优化SQL查询性能,但是具体要如何优化呢?从什么方向上优化呢?

本文通过如何设计订单表ID为起点,引出关于如何设计主键保证数据库查询性能所引起的血案,来逐步发掘 "MySQL 数据库索引设计中" 的真相,一步步解析 MySQL 的索引相关知识点


思路

主键设计 => 数据库查询性能 => 索引性能 => 如何充分利用索引 => MySQL索引架构 => 主键索引 => 二级索引 => 联合索引 => 覆盖索引

DBMS 二级索引结构

数据库中,二级索引(Secondary Index)是除了主键索引之外的索引

MySQL中主键索引是属于聚簇索引,由于聚簇索引中索引和数据物理上粘连存储,所以索引是逻辑并物理有序的,也就是说主键索引不仅在B+树上是有序的,而且在磁盘中的存储位置也是有序排列的。

由于磁盘读取是每次按块读取的,物理有序的主键索引 也都会连续地 存储在同一磁盘块上 ,磁盘读取命中率就会大大提高,减少磁盘IO次数提高读写性能

而二级索引就只是逻辑上有序 ,通过磁盘中存储的一个数据结构组织数据的具体地址,而不是物理数据,是一种非聚簇索引。二级索引在这里作为补充方案,对部分常用的字段查找性能进行有取舍的优化

MySQL实现

在MySQL中的二级索引实现,是通过B+树存储二级索引结,在叶节点存储对应数据的主键ID。当使用二级索引进行查找对应数据时,会进入到对应的索引B+树中进行查找,然后从对应叶节点获取主键ID进行回表查询

回表查询

MySQL的二级索引的叶节点,存储的并不是数据的磁盘物理地址,而是主键的值,通过主键值再从主键索引中查询数据。

因此对于这个特性可以针对性地优化SQL语句:减少查询字段,

sql 复制代码
SELECT * FROM users WHERE name = 'Alice';
sql 复制代码
SELECT id, name FROM users WHERE name = 'Alice';

第一条SQL中select所有列,就需要回表查询所有数据。而第二条SQL中,首先是通过 name = 'Alice' 获取到了name,然后在name字段的二级索引中,获取到了id。由于到这里已经获取到所有需要的数据了,因此直接返回而没有进行回表查询。

联合索引

相较于前面提到的索引,都是单个字段 组成的索引,而我们可以还可以将多个字段组合在一起变成一个二级索引 ,也就是我们常说的联合索引

这里我们通过一个图表来解释联合索引的构成: 假设我们有一个表 orders,有三个字段:user_id, status, created_at

我们创建了一个联合索引:INDEX idx_order (user_id, status)。从而我们就可以得到如下的一个索引B+树:

通过这张图,我们可以发现联合索引中,按照联合索引的排列顺序 ,整个B+树按照这个排列顺序 有序。换句话说,在这个B+索引树中,user_id全局上有序;但status只在user_id其中内部有序,而整个B+索引树中全局上无序

而这一个特性,也就引出联合索引中的一个关键使用原则:最左匹配原则

最左匹配原则 & 失效

最左匹配原则是指在使用联合索引时,MySQL 会从联合索引的最左边的列开始,依次向右匹配查询条件,直到遇到范围查询(如 ><BETWEEN 等)或不满足条件的列为止。

从刚才的B+联合索引树中,我们可以很容易就理解最左匹配原则的原理: 在这个B+索引树中,user_id全局上有序;但status只在user_id其中内部有序,而整个B+索引树中全局上无序

通过遵守最左匹配原则,我们能大大提高SQL的查询性能,但是为什么有时候我们的最左匹配原则会失效呢?首先是比较常见的失效常见:

  1. 查询条件跳过最左列
  2. 查询条件包含范围查询 & 使用非等值操作符
  3. 查询条件
  4. 索引列参与表达式或函数
  5. 隐式类型转换
  6. 数据分布极端

而这里由于包含范围查询 & 使用非等值操作符导致的失效比较难理解外,其他的都一看就懂,因此不做分析

范围查询 & 非等值操作符导致的联合索引失效
B+树与链表

为什么在B+树中包含范围查询 & 使用非等值操作符会导致联合索引失效?对于这样一个问题,我们要层层刨析、分而治之。

首先,B+树的结构不仅适合精确查询,也十分适合范围查询。因为在B+树中,所有的数据都存储在叶节点中,而且所有的叶节点都必须在同一层上,且B+树的叶节点之间还会有指针相连接前后节点。 因此,根据B+树的特性,我们不妨可以将这个树作为一个链表来看 :B+树的叶节点是一条长长的链表,而在链表之上,有一个索引树指向链表分为一条条的段。似乎可以将B+树作为一种另类的跳表来看

通过上面B+树特性的分析,我们就可以简而明了地理解B+树对范围查询的支持:B+树的叶节点作为链表来看,是全局有序的。只需要通过索引找出对应的开头和结尾,就可以确定范围。

"链表"中的有序性

似乎我们看见了一个故人------全局有序性 ,而与之对应的就是局部有序性。回看我们前面的内容: 在这个B+索引树中,user_id全局上有序;但status只在user_id其中内部有序,而整个B+索引树中全局上无序

我们在对次级索引进行更详细的归纳:次级索引的有序性,是基于==单个==主要索引的有序性之上的。

是的,单个主要索引之上。到了这里问题的答案也就自然引出: user_id > 100中存在多个主要索引,而在这些不同的主要索引之中的次级索引,只在单个主要索引中具有有序性,而在多个索引中、全局上是不具有有序性的。 因此我们就无法用联合索引进行SQL查询WHERE user_id > 100 AND status = 0

举一反三,通过上面的原理WHERE user_id = 100 AND status > 1就可以使用联合索引,首先找到user_id = 100的节点区域,在这个主要索引内部中,status就具有局部的有效性。

最后留个思考题,like的模糊查询呢?

覆盖索引

基于MySQL中回表查询的特性,我们就可以针对特定的SQL语句,创建具有所有需要查找字段的索引 ------覆盖索引

结合前面二级索引,当我们使用覆盖索引进行SQL查询,就可以之间通过B+索引树之间获取到我们全部所需要字段内容,而无需进行回表查询,实现和主键索引一样的性能

总结

至此,通过我们对 MySQL 索引结构、回表查询、联合索引、覆盖索引的学习,我们可以针对特定业务所需要执行的SQL语句,建立对应字段的覆盖索引,就可以同时实现安全性和查询性能的保障了。

相关推荐
t198751285 小时前
解决MySQL删除/var/lib/mysql下的所有文件后无法启动的问题
数据库·mysql·adb
IT北辰8 小时前
用Python+MySQL实战解锁企业财务数据分析
python·mysql·数据分析
AWS官方合作商10 小时前
Amazon RDS for MySQL成本优化:RDS缓存降本实战
数据库·mysql·aws
程序猿小D12 小时前
Java项目:基于SSM框架实现的校园活动资讯网管理系统【ssm+B/S架构+源码+数据库+毕业论文+远程部署】
java·数据库·mysql·spring·毕业设计·ssm框架·校园活动
__風__16 小时前
从本地 Docker 部署的 Dify 中导出知识库内容(1.6版本亲测有效)
人工智能·python·mysql·语言模型
知其然亦知其所以然18 小时前
MySQL社招面试题:索引有哪几种类型?我讲给你听的不只是答案!
后端·mysql·面试
Lx35218 小时前
子查询扁平化技巧:减少嵌套层级的查询重构
sql·mysql
神仙别闹18 小时前
基于JSP+MySQL 实现(Web)毕业设计题目收集系统
java·前端·mysql
正经教主19 小时前
【n8n】mysql凭证设置,及注意问题
数据库·mysql·n8n