
主键就是聚簇索引,查主键=直接定位数据页
MySQL 的 InnoDB 引擎里,只要表有主键,它就自动成为聚簇索引(Clustered Index)——数据行不是随便存的,而是按主键值顺序物理存储在 B+ 树叶子节点上。这意味着 SELECT * FROM t WHERE id = 123 这种查询,InnoDB 不需要回表:B+ 树一路查到叶子页,数据就在那儿。
常见错误现象:EXPLAIN 显示 type=const 且 Extra 为空,说明走的是聚簇索引直达;但如果主键是 UUID 或随机字符串,虽然也聚簇,但会导致页分裂严重、插入变慢——这不是“没走索引”,而是“走对了但写坏了”。
- 主键必须是
NOT NULL+UNIQUE,否则 InnoDB 会悄悄加一个隐藏的row_id当聚簇索引,你完全不可控 - 尽量用自增整数(
BIGINT或INT)做主键:顺序写入、缓存友好、二级索引体积小 - 如果业务强依赖自然键(如订单号),可保留为唯一索引,另设自增
id为主键——别为了“语义清晰”牺牲物理存储结构
用主键范围查询时,B+ 树天然支持高效区间扫描
因为数据按主键排序存储,WHERE id BETWEEN 1000 AND 2000 或 WHERE id > 5000 ORDER BY id LIMIT 100 这类操作,InnoDB 只需定位起始位置,然后在叶子链表上顺序读取,不涉及跳转或重复查找。
性能影响明显:对比非主键字段上的范围查询(比如 created_at),后者即使加了索引,也得先查二级索引拿到主键,再回表查数据——多一次 B+ 树搜索,I/O 翻倍。
- 避免在主键上用函数:例如
WHERE YEAR(id) = 2024会让索引失效(id是数字,YEAR()强制类型转换) - 复合主键要注意顺序:
(a, b)的聚簇索引能高效支撑WHERE a = ?和WHERE a = ? AND b > ?,但对WHERE b = ?完全无效 -
ORDER BY PRIMARY KEY几乎零成本;而ORDER BY other_col即使有索引,也可能触发 filesort
主键被其他索引引用,直接影响二级索引大小和更新开销
InnoDB 的所有二级索引(非聚簇索引)的叶子节点,存的不是行指针,而是对应记录的主键值。所以主键越长,每个二级索引条目就越大,缓存效率越低,更新时维护成本越高。
典型踩坑场景:用 VARCHAR(255) 的 UUID 做主键,又建了 3 个二级索引——每个索引的每条记录都存一份 36 字符的字符串,磁盘和内存占用直接膨胀数倍。
- 主键长度建议控制在 8 字节以内(
BIGINT);超过 16 字节就要警惕 - 删掉无用的二级索引:每个额外索引都会拖慢
INSERT/UPDATE/DELETE - 如果必须用字符串主键,优先选定长、紧凑格式(如 base32 编码的 16 字节二进制 ID),别用带分隔符的 UUID 文本
没有显式主键时,InnoDB 怎么办?
如果你建表时没定义 PRIMARY KEY,也没定义 UNIQUE NOT NULL 列,InnoDB 会自动生成一个隐藏的 6 字节 row_id 作为聚簇索引。这个 row_id 全局递增,但不保证全局唯一(高并发下可能冲突),也不对外暴露,无法用于查询或约束。
后果很实际:你永远没法靠“主键”做高效点查;所有二级索引都引用这个隐藏 row_id,迁移、排查、优化全部失去抓手。
- 执行
SHOW CREATE TABLE t,如果没看到PRIMARY KEY,立刻补上——哪怕只是临时加个id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY FIRST - 不要依赖
SELECT * FROM t LIMIT 1来“假装有主键”:这不能改变存储结构 - ORM 框架生成表时默认不加主键(如某些老版本 Django),上线前务必人工确认
最常被忽略的一点:聚簇索引的优势只在「按主键访问」时成立。一旦查询条件脱离主键,无论你给其他字段加多少索引,都绕不开二级索引 + 回表的开销。设计阶段就该想清楚——哪些查询是高频核心路径,它们是否能落到主键或覆盖索引上。










