MySQL 主键索引与唯一索引在性能和数据一致性上的差异

在 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 不允许 允许(多个)
聚簇 通常否
查询 直接定位 可能回表
写入 顺序插入快 需唯一性检查
插入意向锁 可能间隙锁
外键 可被引用 可被引用
复制 定位行 不保证唯一

设计建议

  1. 每张表都应显式定义主键,推荐自增 BIGINT。
  2. 业务唯一约束用 UNIQUE + NOT NULL。
  3. 避免在频繁更新的列上建唯一索引。
  4. 唯一索引列尽量短,减少二级索引体积。
  5. 高并发插入场景,注意唯一索引带来的间隙锁问题。

理解主键索引与唯一索引的差异,核心在于抓住“聚簇 vs 二级”和“唯一性检查时机”这两个关键点。面试中能讲清这两点,再结合锁和 NULL 的细节,就能给出高分回答。

未经允许不得转载:任鹏个人博客 » MySQL 主键索引与唯一索引在性能和数据一致性上的差异

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏