范式与规范化
1NF/2NF/3NF、冗余与异常。
35 分钟
范式与规范化
表设计太「扁」会有冗余和更新异常;拆太碎又 JOIN 爆炸。范式给出拆表的规则,ORM 映射的是规范化后的结构。
学习目标
- 用例子理解 1NF / 2NF / 3NF
- 识别冗余、插入异常、删除异常
- 知道何时可以适度反范式
反例:未规范化的课表
假设把所有课时标题塞进 cf_track 一列:
cf_track.title = "Java 基础"
cf_track.lesson_titles = "变量,循环,函数" ← 违反 1NF
问题:
| 异常 | 表现 |
|---|---|
| 更新 | 改一个课时标题要改整串字符串 |
| 插入 | 没课程时无法单独存课时 |
| 删除 | 删最后一门课时丢光标题列表 |
本站正确做法是 cf_track + cf_lesson 两表,见 sql-design。
三范式(直觉)
1NF:列原子化,不可再拆(不用逗号分隔多值)。
2NF:非主键列依赖完整主键。联合主键 (track_id, slug) 时,title 应只依赖 slug 而非部分键——通常用 surrogate id 作主键更简单。
3NF:非主键列不依赖其他非主键列。例如不要把 category_name 冗余在 cf_lesson(应通过 track_id JOIN cf_track.category)。
ORM 视角
@Entity
public class Track {
@OneToMany(mappedBy = "track", cascade = CascadeType.ALL)
private List<Lesson> lessons; // 1—N,对应规范化的子表
}
规范化让 每个 Entity 职责单一;反范式(如统计表冗余 category)留给读多写少的报表,见 sql-aggregate。
相关课程
- sql-design — 主键外键实践
- orm-er — 画 ER 图
- pg-ddl — PG 侧建表对照
练习
- 设计「用户—收藏—课程」三表,说明如何满足 3NF
- 举一条
cf_progress记录,列出它依赖哪些外键 - 什么场景你会在
cf_lesson冗余 ?权衡是什么