MySQL主键索引与联合索引区别

32 阅读3分钟

下面按“结构/使用方式/适用场景/常见坑”把 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 列或更多列组成