唯一索引和主键索引的区别?
唯一索引和主键索引的区别?
zhangchangsheng面试考察点
- 索引类型理解:面试官不仅仅是想知道 “有什么区别”,更是想考察你是否理解主键索引(聚簇索引)和唯一索引(二级索引)在存储结构上的根本差异。
- NULL 值处理:考察你是否清楚主键不允许 NULL,而唯一索引可以(但只能有一个),这是很多面试者容易忽略的细节。
- 性能优化意识:理解聚簇索引和非聚簇索引的查询效率差异,能否在设计表结构时做出正确选择。
核心答案
主键索引:一种特殊的唯一索引,每张表只能有一个,用于唯一标识每一行记录,InnoDB 中主键是 聚簇索引。 唯一索引:保证索引列的值唯一,一张表可以有多个唯一索引,InnoDB 中唯一索引是 二级索引(非聚簇索引)。 核心区别对比:
| 对比维度 | 主键索引(PRIMARY) | 唯一索引(UNIQUE) |
|---|---|---|
| 数量限制 | 每表 只能有 1 个 | 每表 可以有多个 |
| NULL 值 | 不允许 NULL | 允许 NULL(但最多 1 个) |
| 存储结构 | 聚簇索引,叶子节点存完整数据 | 二级索引,叶子节点存主键值 |
| 索引回表 | 不需要回表 | 需要回表(查询非索引列时) |
| 创建语法 | PRIMARY KEY (col) | UNIQUE KEY uk_name (col) |
| 是否必须 | 建议有,但不强制 | 按需创建 |
一句话总结:主键是 唯一的聚簇索引,不允许 NULL;唯一索引是 可以有多个的非聚簇索引,允许 NULL。
深度解析
一、存储结构差异:聚簇索引 vs 二级索引
主键索引和唯一索引在 InnoDB 中的存储结构完全不同:
flowchart TB
subgraph PK["主键索引(聚簇索引)"]
P1["根节点"] --> P2["叶子节点(数据页)<br/>id=1 | name='Alice' | age=25 | ...<br/>id=2 | name='Bob' | age=30 | ...<br/>存储完整行数据"]
end
subgraph UK["唯一索引(二级索引)"]
U1["根节点"] --> U2["叶子节点<br/>name='Alice' | 主键 id=1<br/>name='Bob' | 主键 id=2<br/>只存索引列 + 主键值"]
end
PK -.- UK
上图对比了主键索引和唯一索引的存储结构。关键区别在于:
- 主键索引(聚簇索引):叶子节点直接存储 完整的行数据,通过主键查询可以直接获取所有字段,无需回表
- 唯一索引(二级索引):叶子节点只存储 索引列的值 + 主键值,如果查询其他字段,需要拿着主键值回表查询聚簇索引
二、NULL 值处理差异
这是面试中的高频考点:
-- 创建测试表 |
flowchart TB
A["主键查询<br/>SELECT * FROM user WHERE id = 1"] --> B["主键索引树(聚簇索引)<br/>叶子节点存储完整行数据"]
B --> C["直接返回完整数据<br/>扫描 1 棵 B+ 树,性能最优"]
D["唯一索引查询<br/>SELECT * FROM user WHERE email = '[email protected]'"] --> E["唯一索引树<br/>email 命中后得到主键 id = 1"]
E --> F["回表:拿着 id = 1 再查聚簇索引"]
F --> G["返回完整数据<br/>扫描 2 棵 B+ 树,比主键查询多一次 I/O"]
关键结论:
- 主键:绝对不允许 NULL,这是主键的基本约束
- 唯一索引:允许 NULL,且 MySQL 中可以插入多个 NULL 值(因为
NULL != NULL)
三、查询性能差异:回表问题
通过主键查询 vs 通过唯一索引查询的性能差异:
-- 表结构 |
| 插入的值 | 主键索引 | 唯一索引 |
|---|---|---|
| 普通值(如 id = 1、id = 2) | 允许,值必须唯一 | 允许,值必须唯一(如 email = ‘[email protected]‘、‘[email protected]‘ 各只能有一条) |
| NULL | 不允许!主键必须唯一且非空 | 允许,且可以有多个 NULL |
注意:在 SQL 标准中 NULL 不等于 NULL,所以唯一索引的多个 NULL 不算重复。
四、使用场景建议
| 场景 | 推荐索引类型 | 原因 |
|---|---|---|
| 标识每一行记录 | 主键索引 | 每表必须有唯一标识,推荐自增 BIGINT |
| 用户邮箱不能重复 | 唯一索引 | 业务唯一性约束,允许未设置邮箱(NULL) |
| 手机号唯一 | 唯一索引 | 允许用户暂未绑定手机号 |
| 身份证号唯一 | 唯一索引 | 允许 NULL(可能未录入) |
| 联合唯一(用户 + 日期) | 联合唯一索引 | UNIQUE KEY uk_user_date (user_id, date) |
最佳实践:
-- 推荐:使用 BIGINT 自增主键 |
五、如果没有主键会怎样?
InnoDB 要求每张表必须有聚簇索引:
flowchart TB
A{"有显式定义的主键?"} -->|是| B["使用主键作为聚簇索引"]
A -->|否| C{"有非空唯一索引?"}
C -->|是| D["使用第一个非空唯一索引作为聚簇索引"]
C -->|否| E["InnoDB 自动生成隐藏主键<br/>6 字节的 ROW_ID,无法被业务使用<br/>性能不如显式主键"]
E --> F["建议:始终为表定义主键"]
面试高频追问
- 追问一:为什么推荐使用自增主键而不是 UUID?
- 答:自增主键是顺序插入的,B+ 树叶子节点顺序追加,不会产生页分裂;UUID 是无序的,插入会导致频繁页分裂,影响性能。此外,UUID 占用空间大(36 字符 vs 8 字节),降低索引效率。
- 追问二:一张表可以没有主键吗?
- 答:可以,但 InnoDB 会自动选择一个非空唯一索引作为聚簇索引;如果没有合适的,会生成隐藏的 6 字节
ROW_ID。但强烈不建议这样做,应该显式定义主键。
- 答:可以,但 InnoDB 会自动选择一个非空唯一索引作为聚簇索引;如果没有合适的,会生成隐藏的 6 字节
- 追问三:联合主键和联合唯一索引有什么区别?
- 答:本质区别和单列一样——联合主键是聚簇索引,不允许任何列为 NULL;联合唯一索引是二级索引,允许列为 NULL。
常见面试变体
- “主键索引和唯一索引有什么区别?”
- “聚簇索引和非聚簇索引的区别是什么?”
- “MySQL 查询走唯一索引时为什么可能需要回表?”
- “唯一索引允许 NULL 吗?主键呢?”
记忆口诀
主键 vs 唯一索引:
- 主键聚簇:叶子存完整数据,查询不回表
- 唯一二级:叶子存主键值,查询需回表
- NULL 有别:主键不允许,唯一索引可以
- 数量不同:主键唯一一个,唯一索引多个
总结
主键索引是 聚簇索引,每表只能有一个,不允许 NULL,叶子节点存储完整行数据;唯一索引是 二级索引,可以有多个,允许 NULL(可多个),叶子节点存储主键值。查询时主键直接获取数据,唯一索引需要回表。推荐使用 BIGINT 自增主键,业务唯一约束用唯一索引。
声明:本文转载自小哈的博客 《唯一索引和主键索引的区别?》,版权归原文作者所有。转载请保留本声明与原文链接。
评论
匿名评论隐私政策
WalineTwikoo
