在 MySQL 的日常开发和面试中,主键索引(PRIMARY KEY)和唯一索引(UNIQUE KEY)经常被放在一起比较。两者都能保证列值的唯一性,但在底层结构、性能表现和数据一致性约束上存在本质区别。理解这些差异,不仅能帮你应对面试,更能指导实际表结构设计。
一、概念回顾:它们到底是什么
主键索引:一种特殊的唯一索引,但一张表只能有一个主键,且主键列不允许为 NULL。InnoDB 中,主键即聚簇索引(Clustered Index),叶子节点直接存储整行数据。
唯一索引:保证索引列的值唯一,但允许一个或多个 NULL 值(具体取决于存储引擎和版本,InnoDB 中多个 NULL 被视为不相等,因此可以重复)。唯一索引可以是聚簇索引(如果被选为主键),但通常作为二级索引(Secondary Index)存在,叶子节点存储的是主键值。
二、底层结构差异
1. 聚簇索引 vs 二级索引
InnoDB 的表数据本身就是按主键组织的一棵 B+ 树。也就是说:
- 主键索引:叶子节点 = 完整数据行。
- 唯一索引:叶子节点 = 索引列值 + 主键值。查询非索引列时,需要根据主键值回表。
这意味着,通过唯一索引查询数据,通常比通过主键查询多一次“回表”操作(除非覆盖索引)。
2. 辅助索引的存储代价
每建立一个唯一索引,就多一棵 B+ 树。插入、更新、删除时,除了维护主键索引,还要维护所有唯一索引。因此,唯一索引越多,写放大越明显。
三、性能差异
1. 查询性能
- 主键查询:直接定位到聚簇索引叶子节点,一次查找即可拿到整行数据,效率最高。
- 唯一索引查询:先查二级索引找到主键值,再回表查聚簇索引。如果是覆盖索引(查询列都在索引中),则不需要回表,性能接近主键。
面试常问:为什么推荐使用自增主键而不是 UUID?
- 自增主键插入时是顺序写入,B+ 树分裂少,页利用率高。
- UUID 随机插入会导致频繁页分裂和碎片,性能下降。
- 此外,二级索引叶子节点存储主键值,主键越长,二级索引越大。
2. 写入性能
- 主键索引:插入时直接按主键顺序维护聚簇索引。
- 唯一索引:插入前需要检查唯一性。如果唯一索引列没有索引,检查会走全表扫描;有索引则走索引查找。每次写入都要额外维护二级索引。
关键点:唯一索引的唯一性检查是在插入/更新时进行的,这会带来额外的开销。而主键的唯一性由聚簇索引本身保证,检查成本已经包含在插入路径中。
3. 锁与并发
在 InnoDB 中,唯一索引的唯一性检查可能涉及间隙锁(Gap Lock)。例如,在可重复读隔离级别下,插入一条记录时,如果唯一索引列存在,可能会对不存在的区间加间隙锁,导致并发插入阻塞。而主键插入通常只加插入意向锁,冲突概率较低。
四、数据一致性差异
1. NULL 值处理
- 主键:不允许 NULL。这是硬性约束。
- 唯一索引:允许 NULL,且多个 NULL 可以共存(InnoDB 中)。这意味着唯一索引不能保证“非空且唯一”,如果需要非空唯一,必须显式加上 NOT NULL。
面试陷阱:有人问“唯一索引能保证数据唯一吗?”答案是可以,但前提是列值不为 NULL。如果业务上要求非空唯一,必须用 NOT NULL + UNIQUE。
2. 主键不可变 vs 唯一索引可更新
主键一旦确定,通常不建议更新(虽然可以,但代价极高,因为要移动整行数据并更新所有二级索引)。唯一索引列可以更新,但更新时同样要检查唯一性并维护索引。
3. 外键引用
InnoDB 的外键必须引用主键或唯一索引。但主键作为外键引用时,性能更好,因为外键检查直接走聚簇索引。
4. 复制与一致性
在基于行的复制中,主键是定位行的关键。如果表没有主键,InnoDB 会生成一个隐藏的 6 字节 row_id 作为聚簇索引,但这不可见且不唯一。唯一索引不能替代主键在复制中的作用,因为唯一索引允许 NULL,且可能不是聚簇索引。
五、面试高频问题与回答思路
Q1:主键索引和唯一索引在查询时有什么区别?
A:主键查询直接走聚簇索引,一次定位;唯一索引查询走二级索引,可能需要回表。如果查询列被索引覆盖,则不需要回表。
Q2:为什么建议用自增主键而不是唯一索引做主键?
A:自增主键顺序插入,减少页分裂;唯一索引做主键时,如果值随机,会导致聚簇索引频繁分裂,性能下降。此外,唯一索引允许 NULL,做主键需要额外约束。
Q3:唯一索引能保证数据一致性吗?
A:能保证列值唯一(NULL 除外),但不能替代主键。主键还承担了聚簇索引、外键引用、复制定位等职责。
Q4:插入时唯一索引和主键索引的锁行为有何不同?
A:唯一索引插入时可能加间隙锁,导致并发插入阻塞;主键插入通常只加插入意向锁,冲突较小。
Q5:一张表没有主键会怎样?
A:InnoDB 会创建一个隐藏的 row_id 作为聚簇索引,但不可见、不唯一,且无法被外键引用。复制时可能找不到行,导致数据不一致。
六、总结与设计建议
| 维度 | 主键索引 | 唯一索引 |
|---|---|---|
| 数量 | 每表一个 | 可有多个 |
| NULL | 不允许 | 允许(多个) |
| 聚簇 | 是 | 通常否 |
| 查询 | 直接定位 | 可能回表 |
| 写入 | 顺序插入快 | 需唯一性检查 |
| 锁 | 插入意向锁 | 可能间隙锁 |
| 外键 | 可被引用 | 可被引用 |
| 复制 | 定位行 | 不保证唯一 |
设计建议:
- 每张表都应显式定义主键,推荐自增 BIGINT。
- 业务唯一约束用 UNIQUE + NOT NULL。
- 避免在频繁更新的列上建唯一索引。
- 唯一索引列尽量短,减少二级索引体积。
- 高并发插入场景,注意唯一索引带来的间隙锁问题。
理解主键索引与唯一索引的差异,核心在于抓住“聚簇 vs 二级”和“唯一性检查时机”这两个关键点。面试中能讲清这两点,再结合锁和 NULL 的细节,就能给出高分回答。
未经允许不得转载:任鹏个人博客 » MySQL 主键索引与唯一索引在性能和数据一致性上的差异

