数据库规范化
数据库规范化通过明确函数依赖、减少重复事实和拆分不合理结构,降低插入、更新与删除异常。
[!info] related notes
数据库规范化
一句话定义
规范化不是“把表拆得越多越好”,而是根据数据依赖关系让一个事实尽量只在合适的位置表达,从而减少冗余和更新异常。
为什么需要规范化
假设每篇 Article 都重复存作者名字:
article_id | user_id | user_name
1 | 10 | Sean
2 | 10 | Sean
3 | 10 | Sean
如果用户改名,需要同时更新很多行;漏掉一行就出现互相矛盾的事实。
更合理的结构通常是:
users
id | name
10 | Sean
articles
id | user_id
1 | 10
2 | 10
3 | 10
查询时再通过 JOIN 组合。规范化与 JOIN 因此是同一建模取舍的两面。
1NF:原子值与重复组
第一范式强调关系中的每个属性位置应表示该属性域中的一个值,不用“第 1 个电话、第 2 个电话、第 3 个电话”这类重复列来模拟集合:
phone_1
phone_2
phone_3
如果一个 User 可以有多个 Phone,更稳定的关系建模通常是:
User 1 ─── N Phone
这里的“原子”不是物理意义上的不可再切分。例如一个日期内部当然包含年/月/日;关键是对当前关系模型和查询语义而言,它被当作一个域值处理。
现代数据库支持 JSON、ARRAY 并不意味着“使用一次数组就违反 1NF”。是否应该拆表仍取决于该值是否需要独立身份、约束、引用、查询和生命周期;不要把范式口诀机械化。
2NF:消除对复合键一部分的依赖
2NF 主要在复合候选键场景中有意义:非主属性应该依赖整个候选键,而不是只依赖其中一部分。
例如 Enrollment 以 (student_id, course_id) 标识一条选课关系:
student_name
只依赖 student_id,不依赖整个 Enrollment,应放回 Student,而不是重复在每条 Enrollment 中。
3NF:减少传递依赖
如果:
employee_id → department_id
department_id → department_name
那么 department_name 本质上依赖 Department,不应因为 Employee 引用了 Department 就在每个 Employee 行里重复保存。
BCNF
BCNF 比 3NF 更严格,要求每个非平凡函数依赖的决定因素都是候选键。它用于处理某些 3NF 仍可能留下的异常结构;初学阶段先把“函数依赖”和 1NF/2NF/3NF 理清,再深入 BCNF 更合适。
三类异常
不合理冗余常带来:
Update anomaly
→ 同一事实必须改很多处
Insert anomaly
→ 想记录一个事实,却被迫同时提供另一个无关事实
Delete anomaly
→ 删除某条业务记录时意外丢掉唯一的一份其他事实
规范化的真正目标是降低这些异常,而不是追求表数量。
什么时候允许反规范化
为了读性能、报表、搜索或缓存,可以有意识地重复数据,但需要明确:
- 哪个来源是 authoritative source;
- 副本如何同步;
- 允许多长的一致性延迟;
- 如何修复漂移。
“为了性能重复存”是工程权衡;“不知道这是重复事实”则是建模问题。
当前掌握边界
当前已掌握规范化的直觉:减少重复事实、1:N/N:M 拆分、1NF 原子值和常见异常;2NF/3NF/BCNF 的严格函数依赖推导尚未经过专项练习,因此 mastery 暂保持 2。