MySQL 索引底层原理|B + 树、聚簇索引、非聚簇索引、覆盖索引

索引是 MySQL 优化查询最核心的手段。很多开发只会简单加索引,不懂底层 B + 树结构,经常写出索引失效的慢 SQL。

本文从索引的作用、B + 树结构讲起,详解 InnoDB 聚簇索引、非聚簇索引,重点讲解覆盖索引原理,附带索引使用规则、踩坑清单,适合后端开发学习。

前言

数据库查询如果不加索引,执行 SQL 时会全表扫描 ,逐条对比数据,数据量大的时候性能极差。 索引本质就是帮助数据库快速查找数据的一种数据结构 。 MySQL 有多种存储引擎,我们日常开发几乎都用InnoDB 引擎 ,InnoDB 索引底层全部使用B + 树。

注意:MyISAM 也是 B + 树,但 InnoDB 和 MyISAM 索引存储结构差异很大,本文默认讲 InnoDB。

一、为什么选用 B + 树,不用二叉树、红黑树、B 树?

磁盘 IO 是数据库查询最大的性能瓶颈。索引存储在磁盘,读取节点就会产生一次磁盘 IO。

  • 二叉搜索树:数据倾斜会退化成链表,树高不可控;
  • 红黑树:平衡二叉树,树高依然高,数据量大 IO 次数多;
  • B 树:多路平衡树,数据和索引放在同一个节点;
  • B + 树 :多路平衡树,所有数据都存放在叶子节点,非叶子节点只存索引键 + 指针。

B + 树核心特点:

  1. 非叶子节点只存储索引值和指针,不存储完整行数据,单个节点可以存放更多索引,树的高度很低;千万级数据,B + 树树高一般只有 2~3 层;
  2. 所有叶子节点在同一层,叶子节点通过双向链表相连,范围查询非常方便;
  3. 数据全部在叶子节点,查询任何数据都要走到叶子节点,IO 次数稳定。

二、聚簇索引(主键索引)

InnoDB 中,聚簇索引就是主键索引,把整行数据保存在索引的叶子节点。

聚簇:数据和索引放在一起,索引的叶子节点就是真实的表数据。

规则

  1. 表必须有聚簇索引:
    • 如果定义主键,主键就是聚簇索引;
    • 没有主键,自动选唯一非空索引作为聚簇索引;
    • 没有唯一非空索引,MySQL 自动生成隐藏的 rowid 作为聚簇索引。
  2. 一张表只能有一个聚簇索引。

查找流程

执行 select * from user where id = 10;,id 是主键(聚簇索引)

  1. 从 B + 树根节点向下查找,对比索引值;
  2. 一直找到叶子节点;
  3. 叶子节点直接存放完整的一行数据,直接返回。

优点:

  • 主键查询速度最快,找到索引就拿到全部数据;
  • 主键范围查询,叶子节点链表可以顺序遍历。

缺点:

  • 主键尽量不要用无序自增 ID。如果主键随机(如 UUID),插入数据会频繁分裂叶子节点,造成页分裂,性能下降;
  • 聚簇索引占用空间大。

三、非聚簇索引(二级索引 / 普通索引)

普通索引、唯一索引都属于非聚簇索引。

非聚簇索引叶子节点不存完整行数据,只存储索引列的值 + 主键值。

查找流程(回表)

给 name 字段建立普通索引:create index idx_name on user(name); 执行 SQL:select * from user where name='张三';

  1. 在 name 的 B + 树(二级索引)查找,找到叶子节点;
  2. 叶子节点拿到主键 id;
  3. 拿着主键 id,再去聚簇索引 B + 树查询一次完整行数据 ,这个过程叫做回表。

重点:二级索引查询,默认需要两次 B + 树查找:二级索引树 → 聚簇索引树(回表)。

四、覆盖索引(避免回表,优化神器)

覆盖索引:查询需要的所有字段,全部包含在二级索引里面,不需要回表查询聚簇索引。 查询的列,全部在二级索引叶子节点能拿到,就跳过回表操作,性能大幅提升。

示例

表:user (id,name,age,address) 索引:idx_name_age (name,age) 联合索引

SQL:

复制代码
select name,age from user where name='张三';

查询只需要 name、age,这两个字段都在联合索引idx_name_age的叶子节点,不需要回表拿主键再查聚簇索引,这就是覆盖索引。

不满足覆盖索引:

复制代码
select name,age,address from user where name='张三';

address 不在二级索引,拿到主键后必须回表查询。

如何判断是否触发覆盖索引:explain 执行计划里,Extra 字段显示 Using index,代表命中覆盖索引。

覆盖索引优点:

  1. 避免回表,减少一次 B + 树查询,IO 减半;
  2. 二级索引叶子节点数据量小,内存可以缓存更多数据。

注意:不要为了覆盖索引盲目创建大量联合索引,索引会占用磁盘,并且降低插入、更新、删除性能。

五、聚簇索引 vs 非聚簇索引对比表

对比项 聚簇索引(主键索引) 非聚簇索引(二级索引)
叶子节点存储内容 整行全部数据 索引列 + 主键 ID
数量 一张表只能 1 个 可以多个
查询 找到索引直接拿到全部数据 默认需要回表
主键过大影响 主键越大,二级索引占用空间越大 叶子要存储主键,主键越长索引体积越大

六、索引最左前缀原则(补充)

建立联合索引 idx_a_b_c(a,b,c) 索引生效顺序:从左向右匹配,遇到范围查询 (> < between like 前缀模糊) 就停止匹配。

  • where a=1 → 命中
  • where a=1 and b=2 → 命中
  • where b=2 and a=1 → 命中(MySQL 优化器会调整顺序)
  • where a>1 and b=2 → a 是范围,b 索引失效

七、索引失效常见场景

  1. 索引列上做函数运算、隐式类型转换;
  2. like '% 关键字 ',左模糊查询,索引失效;
  3. 联合索引不满足最左前缀;
  4. 使用 or,or 两边有字段没加索引;
  5. MySQL 优化器判断全表扫描比索引更快,会放弃索引。

八、开发踩坑汇总

  1. 主键推荐使用自增整型,不要 UUID,容易造成页分裂;
  2. 主键字段不要设计太长,所有二级索引都会保存主键,主键越长二级索引体积越大;
  3. 不要创建过多索引,索引会减慢增删改;
  4. 覆盖索引是优化手段,不要为了覆盖索引随意创建超长联合索引;
  5. 不要在低基数列(性别、状态只有少量枚举值)建立索引,索引几乎无效。

九、问答

Q1:InnoDB 聚簇索引和非聚簇索引区别?

A:聚簇索引叶子节点存储完整行数据,一张表只能一个;非聚簇索引叶子存储索引列 + 主键,查询到主键后需要回表到聚簇索引获取完整数据。

Q2:什么是回表?什么是覆盖索引?

A:回表:二级索引查询只能拿到主键,再去主键索引查找完整数据。覆盖索引:查询所需字段全部存在二级索引,不需要回表,explain 显示 Using index。

Q3:B + 树对比 B 树优势?

A:B + 树非叶子节点不存数据,节点能存放更多索引,树更低 IO 更少;叶子节点链表,范围查询方便。

Q4:一张表可以有多个聚簇索引吗?

A:不能,InnoDB 一张表只能有一个聚簇索引。

结语

InnoDB 索引底层基于 B + 树,聚簇索引把数据和主键索引绑定在一起,二级索引默认会回表。覆盖索引是重要优化手段,通过建立合适联合索引避免回表,提升查询性能。 写 SQL 的时候,结合 explain 看执行计划,判断索引是否命中、是否触发覆盖索引,避免写出慢 SQL。

相关推荐
全栈陈序员1 小时前
【Python】基础语法入门(二十八)—— 数据库操作入门:用 SQLite 持久化数据
数据库·人工智能·python
jnrjian1 小时前
Oracle RAC crs stop时force 关闭DB,另一个node会立即进行recover
数据库·oracle
次次皮1 小时前
Oracle判空踩坑(对比mysql)
数据库·mysql·oracle
严谨的麻辣烫1 小时前
固定 IP 稳定性如何检测?使用 Python 实现网络连通性与耗时监控
开发语言·数据库·php
牧瀬クリスだ1 小时前
Redis List实战:从队列到最新列表的高效应用
数据库·redis·list
码智社1 小时前
Redis生产环境高频踩坑总结(Java后端视角)
java·数据库·redis
江苏汉软1 小时前
制造企业为什么需要MES
java·数据库·制造·mes系统·mes软件
心.c1 小时前
从“加个缓存”到稳定上线:把 Redis 放回真实业务里
数据库·redis·ai·1024程序员节
benjormin2 小时前
MySQL数据库基础
数据库·mysql