1. 范式定义
**范式(Normalization)**是关系数据库设计的一套标准,用于减少数据冗余、避免更新异常。每提升一级范式,约束条件更严格。
1NF(第一范式):原子性
每个属性值不可再分。
❌ 违反 1NF:
users 表
┌────┬──────┬─────────────────┐
│ id │ name │ phones │
├────┼──────┼─────────────────┤
│ 1 │ 张三 │ 13800000000,139 │ ← 逗号分隔,可再分
└────┴──────┴─────────────────┘
✅ 符合 1NF:
users 表
┌────┬──────┐
│ id │ name │
└────┴──────┘
user_phones 表
┌────────┬─────────────┐
│user_id │ phone │
├────────┼─────────────┤
│ 1 │ 13800000000 │
│ 1 │ 13900000000 │
└────────┴─────────────┘
2NF(第二范式):消除部分依赖
非主属性完全依赖于整个主键(针对复合主键)。
❌ 违反 2NF:
选课表 (student_id, course_id) → 成绩, student_name
student_name 只依赖于 student_id(部分依赖)
✅ 符合 2NF:
选课表: (student_id, course_id) → 成绩
学生表: student_id → student_name
3NF(第三范式):消除传递依赖
非主属性不依赖于其他非主属性。
❌ 违反 3NF:
学生表: student_id → name, dept_id, dept_name
dept_name 依赖于 dept_id(传递依赖:student_id → dept_id → dept_name)
✅ 符合 3NF:
学生表: student_id → name, dept_id
院系表: dept_id → dept_name
BCNF(巴斯-科德范式)
每个决定因素都是候选键。
❌ 违反 BCNF:
教师授课表: (teacher_id, course_id) → classroom
teacher_id → classroom(一位教师只在一个教室上课)
但 teacher_id 不是候选键(一位教师可教多门课)
✅ 符合 BCNF:
拆分:
教师教室表: teacher_id → classroom
授课表: (teacher_id, course_id)
2. 范式速查表
| 范式 | 核心要求 | 解决的问题 |
|---|---|---|
| 1NF | 属性原子不可分 | 重复的组、多值属性 |
| 2NF | 消除部分依赖 | 复合主键中的冗余 |
| 3NF | 消除传递依赖 | 间接依赖导致的冗余 |
| BCNF | 决定因素是候选键 | 主属性对候选键的依赖 |
| 4NF | 消除多值依赖 | 独立多值属性组 |
| 5NF | 消除连接依赖 | 在无损分解下可恢复 |
3. 反范式化
**反范式(Denormalization)**是故意违反范式,引入冗余以换取查询性能。
3.1 反范式场景
| 场景 | 反范式做法 | 收益 | 代价 |
|---|---|---|---|
| 高频读低频写 | 冗余字段(如订单表中缓存用户名) | 避免 JOIN | 写时同步更新 |
| 报表查询 | 预计算汇总表 | 秒级出报表 | 需定时刷新 |
| 历史快照 | 快照表保留当时完整数据 | 追溯历史状态 | 数据膨胀 |
| 全文搜索 | 抽数到 Elasticsearch | 复杂条件快速查询 | 双写一致性 |
| 层级查询 | 物化路径/闭包表 | 避免递归 JOIN | 维护路径字段 |
3.2 冗余同步策略
-- 方案 1:应用层双写
BEGIN;
UPDATE users SET name = '张三' WHERE id = 1;
UPDATE orders SET user_name = '张三' WHERE user_id = 1;
COMMIT;
-- 方案 2:触发器(数据库层)
CREATE TRIGGER sync_user_name
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
UPDATE orders SET user_name = NEW.name WHERE user_id = NEW.id;
END;
-- 方案 3:CDC(最终一致)
Debezium 捕获 users 变更 → Kafka → 消费者更新 orders
-- 方案 4:定时任务
每小时汇总更新冗余字段(适合容忍延迟的场景)
4. 设计原则
数据库设计黄金法则:
1. 先按 3NF 设计,消除明显冗余和异常
2. 评估查询模式(Query Pattern),针对性反范式
3. 反范式时,必须有数据一致性保障方案
4. 记录反范式的理由和补偿机制
5. 定期复盘:反范式带来的收益是否值得维护成本
常见误区:
❌ 盲目追求 3NF(过度设计,JOIN 过多)
❌ 全部反范式(数据一致性问题爆炸)
❌ 不做文档(后人无法理解冗余字段的同步逻辑)
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。