4.1.3 数据表的设计规则——范式理论:1NF/2NF/3NF 规范化过程
一句话破题
范式是数据库设计的"卫生标准"——遵守它能避免数据冗余和更新异常。
为什么需要范式?
不规范的表设计会带来三大问题:
| 问题 | 说明 | 示例 |
|---|---|---|
| 数据冗余 | 相同信息重复存储 | 每条订单都存储完整的客户地址 |
| 更新异常 | 修改数据需要改多处 | 客户改地址,要更新所有相关订单 |
| 删除异常 | 删数据可能丢失有用信息 | 删除唯一订单时,客户信息也没了 |
三大范式速览
| 范式 | 核心要求 | 通俗解释 |
|---|---|---|
| 1NF | 字段不可再分 | 一个格子只放一个值 |
| 2NF | 非主键完全依赖主键 | 所有字段都跟主键直接相关 |
| 3NF | 消除传递依赖 | 字段之间不要"套娃"依赖 |
第一范式:原子性
规则:每个字段的值必须是不可再分的原子值。
反例:
| 订单ID | 商品列表 |
|---|---|
| 001 | 苹果,香蕉,橙子 |
正确做法:
| 订单ID | 商品 |
|---|---|
| 001 | 苹果 |
| 001 | 香蕉 |
| 001 | 橙子 |
或者使用关联表:
prisma
model Order {
id String @id
items OrderItem[]
}
model OrderItem {
id String @id
orderId String
product String
quantity Int
order Order @relation(fields: [orderId], references: [id])
}第二范式:完全依赖
规则:非主键字段必须完全依赖于主键,不能只依赖主键的一部分。
反例(复合主键场景):
| 学生ID | 课程ID | 学生姓名 | 课程名称 | 成绩 |
|---|---|---|---|---|
| S001 | C001 | 张三 | 数学 | 90 |
问题:学生姓名只依赖学生ID,课程名称只依赖课程ID。
正确做法:拆分成三张表
prisma
model Student {
id String @id
name String
scores Score[]
}
model Course {
id String @id
name String
scores Score[]
}
model Score {
studentId String
courseId String
score Int
student Student @relation(fields: [studentId], references: [id])
course Course @relation(fields: [courseId], references: [id])
@@id([studentId, courseId])
}第三范式:消除传递依赖
规则:非主键字段不能依赖于其他非主键字段。
反例:
| 员工ID | 员工姓名 | 部门ID | 部门名称 | 部门地址 |
|---|---|---|---|---|
| E001 | 张三 | D001 | 技术部 | 3楼 |
问题:部门名称和部门地址依赖于部门ID,而不是直接依赖于员工ID。
正确做法:
prisma
model Employee {
id String @id
name String
departmentId String
department Department @relation(fields: [departmentId], references: [id])
}
model Department {
id String @id
name String
location String
employees Employee[]
}规范化的实际效果
规范化前:订单表存储冗余客户信息
订单表:
| 订单ID | 客户ID | 客户姓名 | 客户地址 | 商品 | 金额 |规范化后:
prisma
model Order {
id String @id
customerId String
customer Customer @relation(fields: [customerId], references: [id])
items OrderItem[]
total Decimal
}
model Customer {
id String @id
name String
address String
orders Order[]
}范式检查清单
设计表时,逐项检查:
- [ ] 1NF:所有字段都是原子值?没有数组或逗号分隔的值?
- [ ] 2NF:所有非主键字段都依赖于完整的主键?
- [ ] 3NF:非主键字段之间没有依赖关系?
实际开发中的权衡
范式理论是理想状态,实际开发中需要权衡:
| 严格遵守范式 | 适度违反范式 |
|---|---|
| 数据一致性好 | 查询性能更高 |
| 存储空间小 | 减少 JOIN 操作 |
| 更新方便 | 代码更简单 |
建议:先按范式设计,遇到性能瓶颈再考虑反范式化(下一节详述)。
本节小结
- 1NF:字段值不可再分
- 2NF:非主键完全依赖主键
- 3NF:消除传递依赖
- 遵守范式能避免数据冗余和更新异常
- 实际开发中需要在规范和性能之间权衡
