前言
数据库范式听起来好像很难,其实它就是用来约束表设计的几层“规矩”。遵守这些规矩,主要是为了减少数据冗余,避免数据异常。
一、第一范式(1NF):列不可再分
✅ 核心要求:原子性
表中每一列都必须是不可拆分的最小数据单元,不能一个格子里塞多个数据。
❌ 错误例子
| 学号 | 姓名 | 出生年月日 |
|---|---|---|
| 001 | 张三 | 2000-01-01 |
如果出生年月日在业务中需要拆成年、月、日单独使用,那这一列就不是“最小单元”,违反了 1NF。
✅ 正确做法(二选一)
- 拆成三个列:
出生年、出生月、出生日; - 把
出生年月日当成一个整体,业务上永远不拆分,也符合 1NF。
一句话:列里不能再套列,一个格子只存一个拆不开的值。
二、第二范式(2NF):消除部分依赖
前提:已满足 1NF
✅ 核心要求:所有非主键列必须完全依赖于“整个主键”
如果一个表的主键是由多列组成的联合主键,那么每个非主键列都必须依赖于这个联合主键的全部,不能只依赖其中的一部分。
❌ 错误例子:选课表
主键:(学号, 课程号)
| 学号 | 课程号 | 姓名 | 学分 |
|---|---|---|---|
| 001 | C01 | 张三 | 2 |
| 001 | C02 | 张三 | 3 |
| 002 | C01 | 李四 | 2 |
问题:部分依赖
姓名只依赖于学号(不管选啥课,姓名不变)学分只依赖于课程号(不管谁选 C01,学分都是 2)
🚨 会引发的异常
- 数据冗余:张三选 3 门课,“张三”被存 3 次
- 删除异常:删光所有选课记录,课程的学分信息也跟着丢了
- 插入异常:新课没人选,就无法单独录入课程
- 更新异常:C01 学分要从 2 改 3,所有选 C01 的记录都得改,漏一条就数据不一致
✅ 正确做法:拆成 3 张表
学生表(主键:学号)
| 学号 | 姓名 |
|---|---|
| 001 | 张三 |
| 002 | 李四 |
课程表(主键:课程号)
| 课程号 | 学分 |
|---|---|
| C01 | 2 |
| C02 | 3 |
选课表(主键:学号+课程号)
| 学号 | 课程号 | 成绩 |
|---|---|---|
| 001 | C01 | 80 |
| 001 | C02 | 90 |
现在每一列都只依赖于自己表的完整主键。
一句话:联合主键时,非主键列不能只认主键中的一部分。
三、第三范式(3NF):消除传递依赖
前提:已满足 2NF
✅ 核心要求:非主键列不能“隔代依赖”,必须直接依赖主键
不能出现“主键 → 非主键 A → 非主键 B”这种间接的依赖链。
❌ 错误例子:学生表
主键:学号
| 学号 | 姓名 | 学院名称 | 学院电话 |
|---|---|---|---|
| 001 | 张三 | 计算机学院 | 123456 |
| 002 | 李四 | 计算机学院 | 123456 |
| 003 | 王五 | 文学院 | 654321 |
问题:传递依赖学号 → 学生→ 学院名称 → 学院电话
学院电话实际上是通过学院名称才间接依赖学号的,这就叫“隔代依赖”。
🚨 会引发的异常
- 数据冗余:计算机学院的电话被重复存储
- 更新异常:学院电话从 123456 改成 654321 时,所有该学院学生的记录都得改,漏一条就会出数据不一致的 Bug
✅ 正确做法:拆成 2 张表
学生表(主键:学号)
| 学号 | 姓名 | 学院ID |
|---|---|---|
| 001 | 张三 | 01 |
| 002 | 李四 | 01 |
学院表(主键:学院ID)
| 学院ID | 学院名称 | 学院电话 |
|---|---|---|
| 01 | 计算机学院 | 123456 |
| 02 | 文学院 | 654321 |
现在电话直接依赖于学院ID,学生表只存学院ID,依赖链被切断。
一句话:非主键列之间不能互相依赖,只能和主键直接绑定。
四、反范式化:适当打破规则
实际开发中,一般到 3NF 就足够了。但有时为了性能,会故意保留一些冗余,这叫反范式化——用空间换时间。
典型例子:订单金额
| 订单ID | 单价 | 数量 | 金额 |
|---|---|---|---|
| 1001 | 10 | 3 | 30 |
严格来说,金额可以由单价 × 数量算出来,属于冗余字段,违反了 3NF。
但我们常常会保留它,因为查询统计时直接读金额比临时计算要快得多,这就是以空间换时间的做法。
五、范式化 vs 反范式化 优缺点对比
| 对比维度 | 范式化设计(满足 3NF) | 反范式化设计(保留冗余) |
|---|---|---|
| 数据冗余 | 几乎没有,数据表体积小 | 存在冗余,占用更多存储空间 |
| 更新操作 | 改一处即可,无数据不一致风险 | 需同步更新所有冗余副本,维护成本高 |
| 查询性能 | 复杂查询需多表关联,数据量大时较慢 | 大部分查询可用单表或少量关联完成,速度快 |
| 索引优化 | 多表关联场景下索引优化难度高 | 表结构集中,更容易针对性地建立索引 |
| 数据一致性 | 天然保障 | 必须通过额外逻辑(触发器/业务代码)保障 |
六、总结
- 1NF:列不可再分,追求原子性
- 2NF:消除部分依赖,联合主键时,非主键列必须依赖整个主键
- 3NF:消除传递依赖,非主键列只能直接依赖主键
- 反范式化:故意违反范式,用冗余换性能
数据库设计没有绝对的银弹,范式化保证数据整洁,反范式化提升查询效率,实际项目中要根据业务场景在两者之间找到平衡。