主键索引即聚簇索引,数据行直接存储在B+树叶子节点中,查询时无需回表;二级索引仅存索引列和主键值,需回表获取完整行记录。

主键索引就是聚簇索引,数据行直接存里面
MySQL 的 InnoDB 引擎里,PRIMARY KEY 索引不是“普通索引加个唯一约束”那么简单——它决定了整张表数据的物理存储顺序。也就是说,SELECT * FROM t WHERE id = 123 这种查询,InnoDB 找到 id = 123 对应的 B+ 树叶子节点时,**那一页里就直接躺着完整的行记录**,不用二次回表。
常见错误现象:EXPLAIN 显示 type=const 但 Extra 里还带 Using where,其实是没走对主键,或者字段类型隐式转换导致索引失效。
- 主键必须非空且唯一,不能是
NULL - 如果建表时没显式定义
PRIMARY KEY,InnoDB 会悄悄选一个NOT NULL UNIQUE列当主键;都找不到,就自动生成隐藏的row_id(6 字节),但这会让SELECT * FROM t结果不可预测 - 主键越短越好,比如用
BIGINT代替VARCHAR(36)做主键,B+ 树层级更少,范围扫描更快
二级索引只存「索引列 + 主键值」,查数据要回表
所有非主键的 INDEX(包括 UNIQUE INDEX、FULLTEXT、SPATIAL)都是二级索引。它的 B+ 树叶子节点不存整行数据,只存索引字段值和对应记录的主键值(比如 name 索引里存的是 ('Alice', 105),其中 105 是主键 ID)。
使用场景:当你执行 SELECT name FROM user WHERE name = 'Alice',能直接从二级索引里拿到结果(覆盖索引);但一旦写成 SELECT email FROM user WHERE name = 'Alice',就得拿着 105 再去主键索引里捞一遍完整行——这就是“回表”,IO 开销翻倍。
- 联合二级索引要注意最左前缀:
INDEX (a, b, c)能加速WHERE a=1 AND b=2,但对WHERE b=2无效 - 如果业务经常查
SELECT a,b,c FROM t WHERE x=1,与其让优化器回表,不如建覆盖索引INDEX (x, a, b, c) - 二级索引本身也占空间,
INSERT/UPDATE时要同时维护主键树和所有二级索引树,索引越多写越慢
为什么 ORDER BY 有时走不了索引
排序是否能复用索引,关键看「索引顺序」和「查询条件+排序字段」是否构成连续的最左前缀。比如有联合索引 INDEX (status, create_time):
-
WHERE status = 1 ORDER BY create_time→ 走索引,B+ 树里create_time在同一status下天然有序 -
WHERE status > 0 ORDER BY create_time→ 不走索引排序,因为status > 0匹配多段区间,各段内create_time有序,但段之间无序 -
WHERE status = 1 ORDER BY create_time DESC→ 走索引,InnoDB 支持反向遍历叶子节点
性能影响明显:没走索引排序时,EXPLAIN 的 Extra 会出现 Using filesort,意味着 MySQL 要把结果集捞出来再内存或磁盘排序,数据量大时极易触发 sort_buffer_size 溢出,降级为外部归并排序。
唯一性约束和二级索引的关系容易被忽略
建 UNIQUE INDEX (email),不只是为了查得快,更是为了在 INSERT INTO user(email) VALUES ('a@b.com') 时,引擎能快速判断冲突——这个判断就发生在二级索引的 B+ 树搜索过程中。但如果 email 允许 NULL,注意:MySQL 认为多个 NULL 不违反唯一约束(标准 SQL 行为),所以 UNIQUE(email) 并不等价于“邮箱不能为空且唯一”。
- 想真正强制非空唯一,得写
email VARCHAR(255) NOT NULL UNIQUE - 如果已有大量
NULL数据,再加UNIQUE约束会失败,报错Duplicate entry '' for key 'email'(实际是多个NULL被当成相同值) - 二级唯一索引的插入性能比普通索引略低,因为每次都要做唯一性校验(查一遍索引树),高并发下可能成为瓶颈
最常被绕开的一点:主键索引的 B+ 树结构决定了它无法支持“前导模糊查询”(如 LIKE '%abc'),而二级索引同样不支持——这不是设计缺陷,是 B+ 树的固有特性。想支持这类查询,得换 FULLTEXT 或外部搜索引擎。


















