干后端这些年,被慢查询搞心态的次数不少。每次出问题,DBA丢过来的第一句话基本都是“这个SQL没走索引,加个索引就好了”。后来我自己负责业务库,才真正意识到,MySQL索引不是“加个字段就完事”那么简单:加错了不生效,加多了拖慢写入,最要命的是,明明有索引,执行计划却偏偏不用,线上慢查询照样把你压垮。
既然标题敢写“全网最详细”,这篇文章就尽量把 MySQL 索引从底层数据结构到日常调优、从单列索引到联合索引、从生效场景到失效案例一次讲透。内容不堆概念,重点放在“为什么这样设计”和“实操怎么用”上。无论你是刚学索引的新手,还是被线上慢查询困扰的开发者,这篇文章应该都能给你一些实际帮助。
1. 索引的本质与底层数据结构
1.1 索引是什么:一本书的目录逻辑
从最朴素的角度理解,索引就是数据表的“目录”。在没有索引的情况下,一条 SELECT * FROM user WHERE age = 25 要扫描全表,一行行判断,时间复杂度和数据总量成正比,几百万行就能把接口拖到超时。
而有了索引之后,MySQL 可以不再从第一行开始遍历,而是根据索引结构快速定位到目标行的位置。这个逻辑和查字典一样:你查“索引”这个字,不需要从第一页翻到最后一页,而是先去目录里找它的页码,再直接跳转过去。
但这里有个关键点:索引并不存储全量数据,它只是存储了“索引字段的值 + 对应行记录的物理位置(或主键值)”。这个“位置”,在 InnoDB 里通常就是主键值。理解这一点,后面讲回表、覆盖索引的时候就不会懵。
1.2 为什么选 B+ 树而不是二叉树或哈希表
MySQL 的 InnoDB 存储引擎默认使用 B+ 树作为索引结构。为什么不是二叉树、红黑树或者哈希表?这里面涉及到磁盘 IO 的特性。
我们先看磁盘存储的物理现实:MySQL 的数据最终还是落在磁盘上,而磁盘顺序读写快、随机读写慢。操作系统一次从磁盘读数据,是以“页”(默认 16KB)为单位加载到内存的。如果一个树形结构每一层只能存一个数据项,那么几百万行数据可能需要十几层的查找深度,每一层都面临一次磁盘 IO,性能不可接受。
二叉查找树在极端情况下会退化成链表,自平衡的 AVL 树 / 红黑树虽然能控制树高,但每个节点只能存一个元素,树的层数仍然很深。为了再加大每个节点的存储容量,B+ 树的核心设计就是:一个节点可以存储多个元素,并且叶子节点之间用指针串联。
B+ 树的几个关键特征:
- 内部节点只存索引键值,不存数据,因此一页可以容纳成百上千个键值,树的高度被压得很低。通常两三层的 B+ 树就能支撑千万级数据量。
- 叶子节点存储完整的主键值和该行对应的聚簇索引位置,并且叶子节点之间按顺序用双向指针连接。
- 数据存储是有序排列的,所以支持高效的区间查询(
BETWEEN、>、<等)和排序操作。
相对于哈希索引,B+ 树索引最大的优势是支持范围查询。哈希索引因为把键值散列成哈希码后是无序的,只能做精确匹配 = 或 IN,不支持排序,也不支持范围查找。所以即便哈希查找单条记录是 O(1) 复杂度,日常 OLTP 场景中用的依然还是 B+ 树,哈希索引在 InnoDB 中只作为自适应哈希索引存在,由引擎自动判断是否需要建立。
1.3 InnoDB 聚簇索引与二级索引的存储区别
说到 InnoDB 的索引,必须先区分聚簇索引和二级索引,这是我当年踩坑最多的概念之一。
InnoDB 规定:每张表必须且只能有一个聚簇索引。当我们定义了主键,主键索引就是聚簇索引;如果没有主键,MySQL 会找一个非空唯一索引作为聚簇索引;如果两者都没有,InnoDB 会隐式生成一个 6 字节的 row_id 作为聚簇索引。
聚簇索引的叶子节点直接保存了整行数据。也就是说,主键索引本身就是表数据本身,这也解释了为什么 InnoDB 表必须要有主键——没有主键,物理存储就无法组织。
除聚簇索引以外的索引统称为二级索引(也叫辅助索引或非聚簇索引)。二级索引的叶子节点存的是“索引列的值 + 主键值”,并不包含整行数据。所以,当我们通过二级索引查找一条数据时,会先走二级索引树查到主键值,然后再根据主键值去聚簇索引树里搜索完整行。这个二次查找的过程就叫“回表”。
举一个例子:
sql复制CREATE TABLE user (
id BIGINT PRIMARY KEY,
name VARCHAR(50),
age INT,
KEY idx_name (name)
);
如果执行 SELECT * FROM user WHERE name = '张三',MySQL 的步骤是:
- 在
idx_name这棵 B+ 树中,找到name = '张三'对应的叶子节点; - 从叶子节点拿到主键
id; - 再通过主键
id去聚簇索引树中查找完整行数据。
也就是说,这条 SQL 总共搜索了两棵 B+ 树。如果二级索引查询走了 3 层树高,回表又走了 3 层树高,一次查询就是 6 次逻辑 IO。数据量越大,回表性能损耗越明显。解决回表的办法就是后面会讲的覆盖索引。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 索引分类与创建语法
2.1 按功能分类:普通索引、唯一索引、主键索引、全文索引
MySQL 的索引可以按功能维度分类。日常开发中我们最常接触的是这几种:
| 索引类型 | 特点 | 常见用途 |
|---|---|---|
| 普通索引(INDEX/KEY) | 只加速查询,不限制重复值 | 高频查询字段 |
| 唯一索引(UNIQUE) | 加速查询 + 字段值唯一约束 | 手机号、邮箱、身份证号 |
| 主键索引(PRIMARY KEY) | 加速查询 + 非空约束 + 唯一约束 | 每张表必须有主键 |
| 全文索引(FULLTEXT) | 支持文本内容的模糊匹配 | 文章标题、正文的关键词检索 |
特别提一下唯一索引。很多同学会混淆唯一索引和唯一约束,实际上在 MySQL 中,建立唯一约束就是建立唯一索引,两者底层是同一个实现。唯一索引在查询优化器中天然更受欢迎,因为优化器知道同值记录最多只有一条,如果能命中唯一索引,扫描到第一条记录就可以直接停止,不需要继续扫描后续行,这对 LIMIT 1 场景特别友好。
全文索引在 InnoDB 中从 MySQL 5.6 开始才被支持,默认使用 ngram 分词插件来支持中文。但它和我们平时理解的搜索引擎分词不同,MySQL 全文索引在数据量大或者分词需求复杂时性能并不可观,很多团队宁可引入 Elasticsearch 也不是没道理的。常规单表模糊查询(LIKE '%keyword%')是走不了全文索引的,甚至普通索引也帮不上忙,这点后面讲失效场景时会详细说。
2.2 按字段数量分类:单列索引与联合索引
单列索引指索引只建立在单独一个字段上。联合索引(复合索引)则是建立在多个字段上,例如 KEY idx_user_age (name, age)。
很多人把联合索引理解为“分别给每个字
