MySQL 唯一索引和主键索引的区别?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

面试官提出这个问题,通常希望考察以下几个层面的理解:

  1. 对索引核心概念的本质理解:是否清楚两种索引最基本的功能(加速查询)和附加的约束。
  2. 对“约束”这一数据库核心特性的掌握:这是问题的关键。面试官想知道你是否能清晰地阐述主键的 PRIMARY KEY 约束和唯一索引的 UNIQUE 约束在唯一性非空性上的根本区别。
  3. 对 InnoDB 存储引擎特性的了解:因为 MySQL 默认使用 InnoDB,面试官不仅仅是想知道定义区别,更是想知道你是否了解主键索引在 InnoDB 中作为聚簇索引的特殊性及其带来的性能影响。
  4. 实际应用与设计能力:能否根据业务场景正确地选择和使用这两种索引,这反映了你的数据库设计经验。

核心答案

它们的核心区别在于 “约束”,其次是 “实现”。可以概括为:主键索引是一种特殊的唯一索引

  1. 约束差异
    • 主键索引: 同时强制 唯一非空(NOT NULL) 两种约束。一张表只能有一个主键。
    • 唯一索引: 只强制 唯一 约束,但允许存在多个 NULL(在唯一约束下,多个 NULL 被视为不相等)。一张表可以有多个唯一索引。
  2. 实现差异(InnoDB 引擎下)
    • 主键索引: 在 InnoDB 中即聚簇索引,其叶子节点存储的是完整的数据行。表数据的物理存储顺序与主键索引的顺序一致。
    • 唯一索引: 是二级索引,其叶子节点存储的是主键值。查询时需要先找到主键,再回表查询主键索引获取完整数据(除非索引覆盖)。

技术深度解析

原理/机制:

  • 聚簇索引的威力与代价: 正因为主键索引的叶子节点就是数据行,通过主键查询速度极快(一次索引查找即可)。但同时,插入数据时,会为了保证数据按主键顺序物理存放,可能触发频繁的页分裂与数据移动,影响写入性能。这也是为什么推荐使用自增、顺序的主键(如 AUTO_INCREMENT)以减少这方面开销。
  • 唯一索引与 NULL: SQL 标准中,NULL 代表“未知的值”。唯一约束要求所有已知值(非 NULL)必须唯一,而多个 NULL 因为彼此“未知”,所以不违反唯一性。这是理解允许 NULL 的关键。
  • 表没有显式主键时: InnoDB 会首先尝试选择一个非空的唯一索引作为聚簇索引。如果也没有,则会自动生成一个隐藏的、名为 GEN_CLUST_INDEX 的 6 字节 ROWID 作为聚簇索引。因此,显式定义主键是一个好习惯

代码示例:

-- 创建一个测试表
CREATE TABLE `user` (
  `id` INT NOT NULL AUTO_INCREMENT,       -- 主键列,非空自增
  `username` VARCHAR(50) NOT NULL,
  `email` VARCHAR(100),
  `phone` VARCHAR(20),
  PRIMARY KEY (`id`),                     -- 主键索引 (聚簇索引)
  UNIQUE KEY `uk_username` (`username`),  -- 唯一索引,不允许重复用户名
  UNIQUE KEY `uk_email` (`email`)         -- 唯一索引,允许 email 为 NULL,且多个 NULL 不冲突
) ENGINE=InnoDB;

-- 合法的插入操作
INSERT INTO `user` (username, email, phone) VALUES ('alice', 'alice@example.com', '123');
INSERT INTO `user` (username, email, phone) VALUES ('bob', NULL, '456'); -- email 为 NULL,允许
INSERT INTO `user` (username, email, phone) VALUES ('charlie', NULL, '789'); -- 再次插入 NULL,允许

-- 以下操作会失败
-- INSERT INTO `user` (username, email) VALUES ('alice', 'other@example.com'); -- 违反 uk_username 唯一约束
-- INSERT INTO `user` (id, username) VALUES (1, 'david'); -- 违反主键 id 的唯一约束(如果id=1已存在)
-- INSERT INTO `user` (username, email) VALUES ('eve', 'alice@example.com'); -- 违反 uk_email 唯一约束(非NULL值重复)

最佳实践与设计考量:

  1. 主键选择: 优先使用与业务无关的自增整型(如 BIGINT UNSIGNED AUTO_INCREMENT)。它占用空间小、顺序插入性能好。如果必须使用业务字段(如订单号),需确保其稳定、唯一且非空。
  2. 唯一索引的使用场景: 用于保证业务上某个字段或字段组合的唯一性,如用户名、邮箱、身份证号等。记住它允许 NULL
  3. 联合主键/唯一索引: 两者都支持多列组合。组合索引的唯一性是基于所有列值的组合来判断的。
  4. 性能考虑: 由于唯一索引是二级索引,在查询非主键字段时,如果该字段有唯一索引,且查询能利用到“覆盖索引”,性能会非常好。反之,则可能需要“回表”。

常见误区

  • 认为唯一索引也不允许 NULL 值。(错误,允许 NULL,且多个 NULL 不冲突)。
  • 为了“性能”而盲目创建大量唯一索引,忽略了维护唯一性约束本身(每次插入、更新都需检查)带来的开销。
  • 在业务上允许为 NULL 且需要唯一性的字段上错误地使用主键索引。

总结

主键索引与唯一索引的核心区别在于主键强制唯一且非空,而唯一索引只强制唯一但允许 NULL;在 InnoDB 中,主键索引就是数据的物理存储本身(聚簇索引),而唯一索引是独立的二级索引结构。在表设计时,应为每张表定义一个合适的主键,并根据业务逻辑的需要在特定字段上创建唯一索引。