聚簇索引和二级索引有什么区别?什么是「回表」?
简化版
在 InnoDB 里,索引都是 B+ 树,区别在叶子节点存什么:聚簇索引(主键索引)的叶子存整行数据,数据就跟着主键一起组织,一张表只有一个;二级索引(非聚簇/辅助索引)的叶子只存主键值。用二级索引查询时,先查到主键,再拿主键回聚簇索引查整行——这个「查两棵树」的过程叫回表。如果二级索引已包含要查的列,就不用回表,叫覆盖索引。
详细版
聚簇索引(Clustered Index):
- 叶子节点直接存放整行数据,表数据本身就按主键顺序组织在这棵 B+ 树里。
- 一张表只有一个聚簇索引(就是主键;没主键则用唯一非空索引,再没有则 InnoDB 内部生成隐藏 rowid)。
- 「聚簇」= 数据和主键索引聚在一起。
二级索引(Secondary Index,非聚簇索引):
- 叶子节点存索引列 + 主键值(不存整行数据)。
- 可以有多个(每个非主键索引都是一棵二级索引 B+ 树)。
回表(Back to table):
SELECT * FROM t WHERE name = 'Tom' (name 上有二级索引)
1) 在 name 的二级索引 B+ 树里查到 'Tom' → 得到主键 id = 100
2) 再拿 id=100 去聚簇索引 B+ 树里查出整行 ← 这一步就是「回表」
完整版教学
一、两种索引的本质区别:叶子存什么
InnoDB 的所有索引都是 B+ 树,结构一样,唯一区别在叶子节点存的内容:
- 聚簇索引叶子 = 整行数据。所以在 InnoDB 里,「表」和「主键索引」是同一个东西——数据就是按主键组织成的 B+ 树,没有独立的「数据文件」。
- 二级索引叶子 = 主键值(加索引列本身)。它不存整行,只存「怎么找到整行的钥匙(主键)」。
二、为什么二级索引存主键而不是行地址
二级索引叶子存的是主键值,而不是「行的物理地址」。这样设计的好处是:当某行数据因为页分裂、更新而物理位置改变时,只需维护聚簇索引,二级索引不用改(主键值没变)。代价就是查询要多一步「用主键回聚簇索引找行」——即回表。这是一个「牺牲一点查询速度、换取数据移动时的维护简单」的权衡。
三、回表:二级索引查询的额外代价
用二级索引查询非索引列时,要走两棵 B+ 树:
- 先在二级索引里按条件查到主键值。
- 再拿主键值去聚簇索引里查出完整的行。
第二步就是回表。它意味着一次二级索引查询实际上要查两棵树、多几次 IO。如果结果行很多,回表次数多,开销显著(这也是「深分页」「大范围二级索引查询」慢的原因之一)。
四、覆盖索引:避免回表
如果一个查询需要的列,二级索引里全都有(索引列 + 主键),就不用回表——直接在二级索引这棵树里就取到了全部结果。这叫覆盖索引(Covering Index):
-- name 上有二级索引,叶子存 (name, id)
SELECT id, name FROM t WHERE name = 'Tom'; -- 只要 id 和 name
-- 二级索引叶子里就有 name 和 id,不用回表 → 覆盖索引,快
所以实践中常通过建联合索引把常查的列包进去,让查询「被索引覆盖」,避免回表。EXPLAIN 里看到 Using index 就表示用上了覆盖索引。
五、对建表和查询的启示
SELECT *容易触发回表:要了不在索引里的列,就得回表;只SELECT需要的列更可能命中覆盖索引。- 主键要短:二级索引叶子都存了主键,主键越大,每个二级索引也越大。短主键让所有二级索引都受益。
- 联合索引的列顺序:把查询和排序常用的列放进联合索引,能同时利用最左前缀 + 覆盖索引,减少回表。
六、常见误区与追问
| 考点 | 正确口径 |
|---|---|
| 聚簇索引 | 叶子节点存整行数据 |
| 二级索引 | 叶子节点存索引列和主键值 |
| 回表 | 用二级索引找到主键后再查聚簇索引取整行 |
secondary index (idx_name):
name -> primary_key
clustered index:
primary_key -> full row
回表不是多查一次二级索引,而是拿二级索引里的主键再去聚簇索引查整行。
- 误区:二级索引叶子节点直接存整行。 InnoDB 二级索引叶子通常存主键值,完整行在聚簇索引叶子页。
- 误区:使用二级索引一定要回表。 如果查询列都在二级索引中,形成覆盖索引,就不需要回表。
- 误区:聚簇索引可以有很多个。 表的数据物理组织只能按一种聚簇方式存放,通常聚簇索引只有一个。
- 追问:为什么二级索引存主键而不是行地址? 主键稳定表达行位置,页分裂移动时不需要更新所有二级索引的物理地址。
- 追问:回表代价为什么高? 二级索引查一次 B+ 树后,还要按主键再查聚簇索引,随机 IO 和页访问增加。
- 追问:如何减少回表? 设计覆盖索引、减少
select *、让查询列尽量被索引包含。
七、加强记忆
InnoDB 索引都是 B+ 树,区别在叶子:聚簇索引(主键)叶子存整行数据,一表一个,数据即索引;二级索引叶子存主键值,可多个。用二级索引查非索引列,要先查到主键、再回聚簇索引取整行,这叫回表(查两棵树)。若二级索引已包含所需全部列,则覆盖索引,免回表。启示:少用 SELECT *、主键要短、善用联合索引凑覆盖。