下面按“结构/使用方式/适用场景/常见坑”把 MySQL 主键索引 和 联合索引(复合索引) 的区别讲清楚(以 InnoDB 为主)。
1)本质区别
-
主键索引(PRIMARY KEY)
- 是 聚簇索引(clustered index) :叶子节点存整行数据。
- 一张表只能有一个 主键索引。
- 主键值必须 唯一且非 NULL。
-
联合索引((a,b,c) 这种)
- 是索引“键”的组合(可以是普通索引、唯一索引等)。
- 在 InnoDB 里通常是 二级索引(secondary index) :叶子节点存(联合索引键 + 主键值) ,回表再拿整行(除非覆盖索引)。
- 一张表可以有多个联合索引。
- 是否唯一、是否允许 NULL 取决于你建的是
UNIQUE还是普通INDEX,以及列是否允许 NULL。
2)查询命中方式差异(最关键:最左前缀)
联合索引遵循 最左前缀原则:
假设有联合索引:idx(a,b,c)
✅ 能用上索引的典型情况:
where a = ?where a = ? and b = ?where a = ? and b = ? and c = ?where a = ? and b > ?(用到 a,b,c 用不上)
❌ 很可能用不上(或只能用部分):
where b = ?(缺少最左列 a)where c = ?(缺少最左列 a)where b = ? and c = ?(仍缺 a)where a > ? and b = ?(范围条件后面的列通常难再继续用来精确过滤)
而主键索引命中就很直接:
where id = ?走主键索引,通常是最强的点查路径(唯一、聚簇、树高低)。
3)性能与回表差异
-
主键索引查询
- 命中后叶子节点就是整行数据,不需要回表。
-
联合索引(二级索引)查询
- 命中后先拿到主键值,再去主键索引取整行:需要回表。
- 但如果查询的列都在索引里(联合索引覆盖了 select 列),就能 覆盖索引:不回表,性能很高。
例子(覆盖索引):
- 有
idx(a,b,c) select a,b from t where a=? and b=?→ 可能直接在索引叶子拿到需要列,不回表。
4)约束语义不同
- 主键:强约束(唯一 + 非空),并且影响 InnoDB 行存储组织方式(数据按主键顺序组织)。
- 联合索引:更多是为查询/排序/分组服务;若是
UNIQUE(a,b)才带“组合唯一”的约束语义。
5)设计建议(实战)
-
主键建议:短、单调递增、稳定(如 BIGINT 自增或雪花 ID),避免用长字符串做主键(会导致二级索引叶子也更大,因为二级索引叶子要存主键值)。
-
联合索引建议:
- 把区分度高、常用过滤的列放左边(但也要看查询模式)。
- 让索引尽量同时服务
where + order by + group by(避免 filesort / 临时表)。 - 能覆盖就覆盖:把常用
select字段尽量放进联合索引(但别为了覆盖无限加列,索引会变胖,写入变慢)。
6)一句话总结
- 主键索引:表的“数据本体”组织方式,点查最强、只能一个、唯一非空、叶子存整行。
- 联合索引:为多条件查询优化的“组合键”索引,遵循最左前缀,通常是二级索引,可能回表,能做覆盖索引。
| 特性 | 主键索引 (Primary Key) | 联合索引 (Composite Index) |
|---|---|---|
| 数量限制 | 一张表只能有一个 | 一张表可以有多个 |
| 是否允许为空 | 不允许 (NOT NULL) | 允许 (除非定义列时指定 NOT NULL) |
| 唯一性 | 必须唯一 | 可以不唯一(除非定义为联合唯一索引) |
| 存储结构 | 聚簇索引:叶子节点存的是整行数据 | 辅助索引:叶子节点存的是主键值 |
| 组成列数 | 通常为 1 列(也可以是复合主键) | 由 2 列或更多列组成 |