深蓝黑背景封面:右侧大标题「索引的种类,一张图理清」,副标题「聚簇 / 非聚簇 / 覆盖 / 联合 / 前缀」;左侧原创示意——一棵三层 B+ 树,叶子层一行连着整行数据、另一行只连主键,对比聚簇与非聚簇的差别;左上标注「面试经典 · 数据库」,右上标注「Day 02 · 10-20」 图 1:本文封面,叶子节点连整行还是只连主键,是索引世界的第一道分水岭。

先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。「索引的工作原理及种类」是 MySQL 面试的主干题,很多人能罗列一堆名词,但混在一起讲——其实它们分属两个坐标轴:一个讲数据怎么组织,一个讲索引怎么服务查询。

想象面试现场:「MySQL 的索引有哪些种类?聚簇索引和非聚簇索引什么区别?」

第一个坐标轴:按数据组织,分聚簇与二级。

以 InnoDB 为准(MySQL 默认引擎)。聚簇索引的叶子节点直接挂着整行数据——索引即数据,找到索引就拿到了行。每张表只有一个聚簇索引,选择规则官方文档写得很清楚:有主键就用主键;没有主键就用第一个所有列都非空的唯一索引;两者都没有,InnoDB 会自己造一个隐藏的聚簇索引,建在单调递增的 6 字节 row ID 上。所以「给表定义一个短主键」不是审美建议,是性能建议。

除聚簇索引外的都是二级索引(也叫非聚簇、辅助索引)。关键设计:二级索引的叶子节点存的不是行指针,而是「索引键加这行的主键值」。用二级索引查到主键后,还要回到聚簇索引再查一次,才能拿到整行——这个动作就是回表。一次普通查询走两棵树,这就是二级索引的隐性成本。也因此,主键越长,每张二级索引的叶子都要跟着变胖,官方文档的建议是主键尽量短。

第二个坐标轴:按功能,分覆盖、联合、前缀。

覆盖索引不是一种物理结构,而是一种查询状态:SELECT 要的列全部落在索引里,引擎直接从索引取值,不回表。官方术语表的说法是「查询结果完全从索引结构返回,省下磁盘 I/O」。高并发读场景里,把热点查询的列凑进一个索引,省掉的回表是实打实的延迟优化。

联合索引是多列组合成一个索引,如 INDEX(last_name, first_name)。它的匹配规则叫最左前缀:官方文档明确,索引 (col1, col2, col3) 能服务 (col1)、(col1, col2)、(col1, col2, col3) 的查询,但 WHERE 里只出现 col2 或 col2+col3 就用不上。可以把联合索引理解成按「先姓后名」排序的电话簿:查「姓张的」很快,查「名叫伟的」只能全翻。设计列序时等值列在前、范围列放最后,是因为范围条件之后的列就断了有序性,后面的列只能用于过滤,不能再精确定位。

前缀索引是对长字符串只索引前若干个字符:INDEX(email(20))。拿一点区分度换索引体积,适合邮箱、URL 这类长列;代价是前缀部分相同的值会聚在一起,且前缀索引无法用于覆盖——索引里没有完整列值,该回表还得回。

一张表把四个概念放回原位。

CREATE TABLE orders (
  id BIGINT PRIMARY KEY,              -- 聚簇索引:叶子挂整行
  user_id BIGINT NOT NULL,
  status VARCHAR(16) NOT NULL,
  email VARCHAR(255),
  amount DECIMAL(10,2),
  KEY idx_user_status (user_id, status),  -- 联合索引:最左前缀匹配
  KEY idx_email (email(20))               -- 前缀索引:只索引前 20 字符
);

-- 覆盖索引:要的列都在 idx_user_status 里,免回表
SELECT user_id, status FROM orders WHERE user_id = 1001;

-- 要 amount,索引里没有,先查二级索引拿主键,再回聚簇索引取整行
SELECT amount FROM orders WHERE user_id = 1001;

谱系加机制图:上方索引谱系分两支——按数据组织分,聚簇索引叶子直接挂整行数据(一张表只有一个,通常就是主键),二级索引叶子只挂索引键加主键值;按功能分,覆盖索引指查询列全部落在索引里免回表,联合索引多列组合按最左前缀匹配,前缀索引只索引长字符串的前若干字符省空间。下方回表演示:二级索引先查到主键,再回聚簇索引取整行,一次查询走两棵树;覆盖索引把要查的列直接放进索引,第二棵树省掉。底部提示:联合索引的列序按最左前缀设计,范围列放最后 图 2:索引谱系的两个坐标轴与回表机制演示。

可能的追问与易错点。

「为什么二级索引叶子不直接存行地址?」因为行在页之间会移动(页分裂、合并),存物理地址意味着每次行移动都要更新所有二级索引,代价太大;存主键值,行搬家时二级索引纹丝不动,代价转化为查询时的一次回表——这是用查询开销换写入稳定性的经典取舍。「联合索引列序怎么定?」等值查询的列往前放,区分度高的列优先,范围列压轴。易错点集中在三处:把覆盖索引当成一种要单独 CREATE 的结构(它是查询与索引的匹配状态);以为二级索引直接挂行指针,讲不出回表;给表设又臭又长的联合主键,拖累所有二级索引变胖。

这套谱系在 AI 工程的数据侧同样高频:样本表、向量元数据表、实验记录表,查询加速都得先选对索引类型;高并发读接口把 SELECT 列收成覆盖索引,延迟立竿见影。回到面试现场,建议的答题节奏:先立「数据组织」和「查询功能」两个坐标轴,再讲清二级索引叶子存主键与回表,然后覆盖、联合、前缀各给一句话原理加一个例子,最后主动补「主键要短」这个工程建议。你在生产库里靠覆盖索引优化过慢查询吗?效果怎么样?欢迎分享。