聊到 MySQL 优化,索引永远是绕不开的第一话题。很多后端同学对“B+树、聚簇索引、最左前缀”这些词都能背出来,但一上完 explain 就懵,或者线上慢查询出现时不知道怎么判断“这个字段到底该不该建索引”。这篇文章我打算把 MySQL 索引的底层逻辑、日常建索引的实操原则、索引失效的排查思路、常见高频面试问题一次性讲透。内容有些长,尤其适合刚接触数据库优化的人,或者准备面试想把 MySQL 索引这个点补全的同学,全文没有废话,纯按我平时实际排查问题的思路来写。
1. 索引的底层原理:为什么索引能快这么多
1.1 从一次全表扫描说起
我遇到过不少开发同学,刚开始对索引的理解就是“给字段加个索引,查询就变快了”。如果只停留在这一层,遇到实际的慢 SQL 还是会很吃力。先想想没有索引时,MySQL 是怎么找一条数据的。
大多数情况下,InnoDB 的数据是按主键聚簇存放在表空间里的,数据的最小单位是一个个 16KB 的页。查询如果不走索引,优化器只能从第一个页开始,一页一页把数据读进来,逐行匹配 WHERE 条件,这个过程叫全表扫描。数据量小的时候没感觉,一旦表达到千万行,这种方式的耗时几乎正比于总行数,慢是必然的。
这就像一本没有目录的词典,你要找一个词,只能从第 1 页翻到最后一页。索引相当于给数据按某个字段重新整理了一份“有序目录”,而且这份目录本身也以树形结构存在硬盘上,方便快速定位。
但索引真正强的地方,不是因为建立了“目录”本身,而是目录内部采用了一种特殊的数据结构:B+ 树。
1.2 谈一下 B+ 树的排序原理
B+ 树你可以理解成一棵多叉平衡搜索树。为什么要多叉?因为 MySQL 在磁盘上读取数据的最小单位是页,一页 16KB,IO 开销按“次”算,很不便宜。树的高度越低,定位一条数据需要的磁盘随机读次数就越少。
二叉树的问题在于:数据有序插入时很容易退化成一条链表,即便用红黑树等自平衡方案,树的高度大约是 O(log2N)。一千万数据大概需要 20 多次比较,每次都对应一次磁盘 IO,这个成本是难以接受的。B+ 树每个节点可以放很多“索引键 + 子页指针”,一个 16KB 的叶子页能容纳的键数量可以上千,正常情况下两到三层就能覆盖千万级数据。
B+ 树有一个非常关键的细节:非叶子节点只存索引键和子页指针,不存业务数据。这样的好处是一个节点能放出更多的分叉,树更矮。最终所有数据都集中在叶子节点,叶子节点内部按索引键有序排列,叶子页之间又通过链表相连。所以范围查询会特别舒服:先定位到边界,然后顺着叶子链表一路往下扫,不需要频繁回溯上层。
你平时看到的 WHERE id BETWEEN 100 AND 1000 能很快执行完,底层靠的就是这个链表设计。相比普通 B 树,B+ 树在范围查询上的优势非常明显,这也是 InnoDB 选它做索引结构的重要原因。
1.3 InnoDB 中“聚簇索引”和“二级索引”
很多人分不清主键索引和普通索引,其实在 InnoDB 里,每个表的数据本身就是一棵 B+ 树,树的叶子节点存的是整行记录,这个索引就叫聚簇索引。
建表时如果指定了主键,InnoDB 一般会把主键作为聚簇索引的键;没有主键但有非空唯一索引,会选择第一个这样的唯一索引;两个都没有,InnoDB 会生成一个隐藏的 6 字节 rowid 作为聚簇索引键。
你手动添加的普通索引、唯一索引、联合索引都叫二级索引。二级索引与聚簇索引的最大区别在于:它的叶子节点不存整行数据,只存“索引键 + 主键值”。
举例来说,如果我在 user 表的 user_name 字段上建索引,那么这棵索引树的叶子节点内容是 (user_name, id)。当有一条 WHERE user_name = '张三' 的查询时,MySQL 会先去 user_name 这棵索引树找到对应的叶子,拿到主键 id,再拿着这个 id 去聚簇索引树里查完整记录。
1.4 聚簇索引回表细节
上面这个“先查二级索引,再查聚簇索引”的过程,在 MySQL 里称为回表。回表不是免费的,每回表一行,都需要走一次聚簇索引树查找。假如二级索引命中了 1000 行,可能就会产生 1000 次随机的聚簇索引查询,这个成本偏高。
所以你会经常听人建议少用 SELECT *。如果查询需要返回的列,刚好都包含在某个二级索引里,就可以只扫这棵索引树,拿到所有需要的字段后直接返回,完全不用回表,这种状态叫覆盖索引。后续我们会专门展开。
这里先记住一个结论:InnoDB 的二级索引叶子节点存储的是主键值,这是 InnoDB 设计上的一大特点。好处是当数据页发生分裂、页内记录移动时,二级索引不需要跟着改存储位置;代价就是大多数普通索引查询都会多一次回表操作。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 索引的类型与创建实操
2.1 一张表里常见索引类型
MySQL 的索引类型从使用角度可以划分为以下几类:
| 索引类型 | 特点 | 典型场景 |
|---|---|---|
| 主键索引 | 聚簇索引,非空唯一,通常每表一个 | id 字段 |
| 唯一索引 | 允许 NULL,NULL 可以有多个 | 手机号、邮箱等业务唯一标识 |
| 普通索引 | 只为了加速查询,不约束唯一性 | 高频 WHERE 字段 |
| 联合索引 | 多个字段组成一棵索引树 | 多条件等值查询、排序 |
| 全文索引 | 针对长文本做分词匹配 | 文章标题、正文搜索 |
| 前缀索引 | 只对字段前 N 个字符建立索引 | 超长字符串字段 |
主键索引和唯一索引很容易混淆:主键索引本质上也是唯一索引,但主键列不允许 NULL,而且 InnoDB 直接拿主键当聚簇索引来组织整张表的数据。唯一索引只是数据约束层面的唯一性保证,它本质上仍然
