MySQL 唯一索引和主键索引的区别?
2025年12月27日
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 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/
面试考察点
面试官提出这个问题,通常希望考察以下几个层面的理解:
- 对索引核心概念的本质理解:是否清楚两种索引最基本的功能(加速查询)和附加的约束。
- 对“约束”这一数据库核心特性的掌握:这是问题的关键。面试官想知道你是否能清晰地阐述主键的
PRIMARY KEY约束和唯一索引的UNIQUE约束在唯一性和非空性上的根本区别。 - 对 InnoDB 存储引擎特性的了解:因为 MySQL 默认使用 InnoDB,面试官不仅仅是想知道定义区别,更是想知道你是否了解主键索引在 InnoDB 中作为聚簇索引的特殊性及其带来的性能影响。
- 实际应用与设计能力:能否根据业务场景正确地选择和使用这两种索引,这反映了你的数据库设计经验。
核心答案
它们的核心区别在于 “约束”,其次是 “实现”。可以概括为:主键索引是一种特殊的唯一索引。
- 约束差异:
- 主键索引: 同时强制
唯一和非空(NOT NULL)两种约束。一张表只能有一个主键。 - 唯一索引: 只强制
唯一约束,但允许存在多个NULL值(在唯一约束下,多个NULL被视为不相等)。一张表可以有多个唯一索引。
- 主键索引: 同时强制
- 实现差异(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值重复)
最佳实践与设计考量:
- 主键选择: 优先使用与业务无关的自增整型(如
BIGINT UNSIGNED AUTO_INCREMENT)。它占用空间小、顺序插入性能好。如果必须使用业务字段(如订单号),需确保其稳定、唯一且非空。 - 唯一索引的使用场景: 用于保证业务上某个字段或字段组合的唯一性,如用户名、邮箱、身份证号等。记住它允许
NULL。 - 联合主键/唯一索引: 两者都支持多列组合。组合索引的唯一性是基于所有列值的组合来判断的。
- 性能考虑: 由于唯一索引是二级索引,在查询非主键字段时,如果该字段有唯一索引,且查询能利用到“覆盖索引”,性能会非常好。反之,则可能需要“回表”。
常见误区
- 认为唯一索引也不允许
NULL值。(错误,允许NULL,且多个NULL不冲突)。 - 为了“性能”而盲目创建大量唯一索引,忽略了维护唯一性约束本身(每次插入、更新都需检查)带来的开销。
- 在业务上允许为
NULL且需要唯一性的字段上错误地使用主键索引。
总结
主键索引与唯一索引的核心区别在于主键强制唯一且非空,而唯一索引只强制唯一但允许 NULL;在 InnoDB 中,主键索引就是数据的物理存储本身(聚簇索引),而唯一索引是独立的二级索引结构。在表设计时,应为每张表定义一个合适的主键,并根据业务逻辑的需要在特定字段上创建唯一索引。