Featured image of post 数据库反规范化设计方法

数据库反规范化设计方法

数据库反规范化设计方法

系统架构师考试 | 数据库设计专题 对应 2021 年下半年案例分析 试题三 Q1、Q2 阅读时长:约 10 分钟

前言

数据库设计有两大流派:规范化(Normalization)反规范化(Denormalization)。规范化追求"减少冗余、避免异常",但过度规范化会导致大量 JOIN 查询,性能崩塌。反规范化"主动引入冗余"换性能,但带来数据不一致性。本篇系统讲解反规范化的 4 种方法、解决数据不一致性的 3 种方法,以及软考答题套路。


一、规范化的"三范式"回顾

范式要求解决的问题
1NF字段不可分(原子性)重复组
2NF非主属性完全依赖主键(消除部分依赖)部分依赖
3NF非主属性不传递依赖主键(消除传递依赖)传递依赖
BCNF每个决定因素都是候选键主属性对候选键的部分/传递依赖

过度规范化的代价:表越分越细,关联查询越多,性能越差。


二、为什么要反规范化?

核心矛盾

  • 规范化(3NF/BCNF)→ 表细分 → 多表 JOIN → 性能差
  • 反规范化 → 主动引入冗余 → 单表查询 → 性能好 → 有数据不一致风险

典型场景(软考常见):

  1. 报表查询:跨多表的统计 JOIN
  2. 列表页展示:主表 + 关联字典(名称、状态、类型)
  3. 商品详情页:商品 + 分类 + 品牌 + 库存 + 价格
  4. 用户主页:用户 + 等级 + 粉丝数 + 关注数 + 获赞数

三、4 种常见反规范化方法

3.1 增加冗余列(最常用)

做法:在表中保存其他表的字段,避免 JOIN。

示例(2021 真题)

1
2
3
4
5
6
7
8
-- 原始(规范化)
药品(药品ID, 药品名称, 药品型号, 药品价格, 供应商ID)
供应商(供应商ID, 供应商名称, ...)
药品库存(药品ID, 当前库存数量)

-- 反规范化后:把供应商名称 + 当前库存 冗余到药品表
药品(药品ID, 药品名称, 药品型号, 药品价格,
     供应商ID, 供应商名称, 当前库存数量)  -- ← 冗余字段

效果:商品详情查询从 3 表 JOIN 变成 1 表 SELECT。

适用:列表页、详情页、报表统计。

3.2 增加派生列(计算列)

做法:存储计算结果,避免实时计算。

示例

1
2
3
4
5
-- 订单表增加冗余字段
订单(订单ID, 用户ID, 订单金额,
     商品总数,  -- 派生:SUM(订单明细.数量)
     实付金额,  -- 派生:订单金额 - 优惠金额
     优惠金额)  -- 派生:SUM(优惠券.面额)

效果:列表展示不用 SUM/GROUP BY。

适用:订单统计、排行榜、用户主页计数。

3.3 重新组表(垂直合并)

做法:把经常一起查的字段合并到一张宽表。

示例

1
2
3
4
5
6
7
-- 原始:用户基本信息 + 用户扩展信息分两张表
用户(用户ID, 用户名, 邮箱, 手机号)
用户扩展(用户ID, 头像URL, 个人简介, 生日, 性别)

-- 反规范化:合并
用户(用户ID, 用户名, 邮箱, 手机号,
     头像URL, 个人简介, 生日, 性别)  -- ← 合并

适用:查询总是同时访问这些字段。

3.4 分割表(水平 / 垂直拆分)

做法

  • 水平拆分:按行(用户/时间/地区)分到多张结构相同的表
  • 垂直拆分:按字段(冷热)分到多张表

示例

1
2
3
4
5
6
7
8
-- 水平拆分:按年份
订单_2024(订单ID, 用户ID, 金额, ...)
订单_2025(订单ID, 用户ID, 金额, ...)
订单_2026(订单ID, 用户ID, 金额, ...)

-- 垂直拆分:冷热分离
商品_热(商品ID, 名称, 价格, 描述)  -- 频繁访问
商品_冷(商品ID, 详情HTML, 评论JSON) -- 偶尔访问

适用:大数据量表(订单、日志)。


四、解决数据不一致性的 3 种方法

反规范化最大的代价是数据可能不一致——冗余字段没及时更新。3 种解决方案:

4.1 触发器(数据库自动同步)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- 当供应商表更新时,自动同步到药品表
DELIMITER $$
CREATE TRIGGER trg_sync_supplier
AFTER UPDATE ON 供应商
FOR EACH ROW
BEGIN
    UPDATE 药品
    SET 供应商名称 = NEW.供应商名称
    WHERE 供应商ID = NEW.供应商ID;
END$$
DELIMITER ;

优点:实时性强、对应用代码透明、自动同步 缺点:数据库性能开销大、跨库难、调试难

4.2 应用层代码(业务同步)

1
2
3
4
5
6
7
// Service 层同步更新
@Transactional
public void updateSupplier(Supplier supplier) {
    supplierDao.update(supplier);
    // 同步更新冗余字段
    drugDao.updateSupplierName(supplier.getId(), supplier.getName());
}

优点:可控、易调试、不依赖数据库 缺点:所有更新点都得写同步逻辑、易遗漏、分布式事务复杂

4.3 批处理同步(定时修复)

1
2
3
4
5
-- 每天凌晨跑批,修复不一致
UPDATE 药品 p
JOIN 供应商 s ON p.供应商ID = s.供应商ID
SET p.供应商名称 = s.供应商名称
WHERE p.供应商名称 != s.供应商名称;

优点:解耦、不影响在线业务 缺点有延迟(小时/天级)、不一致窗口长

4.4 软考答题选择

场景推荐方法
强实时、字段少、表在同一库触发器(首选)
跨库、跨服务、复杂业务应用层代码 + 消息队列
报表统计、允许延迟批处理同步

2021 真题答案触发器(实时性强、对应用透明、保证一致性)


五、实战:药品销售系统反规范化决策

5.1 原始规范化设计

1
2
3
4
供应商(供应商ID, 供应商名称, 联系方式, 供应商地址)
药品(药品ID, 药品名称, 药品型号, 药品价格, 供应商ID)
药品库存(药品ID, 当前库存数量)
订单(订单号码, 药品ID, 供应商ID, 药品数量, 订单金额)

问题:用户查商品 = 3 表 JOIN(药品 + 供应商 + 库存),关联多、性能差。

5.2 反规范化决策表

冗余字段目标表原表方法同步方式
供应商名称药品供应商增加冗余列触发器(供应商表 UPDATE)
当前库存数量药品药品库存增加冗余列触发器(药品库存表 UPDATE)
药品总销量药品订单(聚合)增加派生列触发器(订单 INSERT 后 SUM)

反规范化后

1
2
药品(药品ID, 药品名称, 药品型号, 药品价格,
     供应商ID, 供应商名称, 当前库存数量, 药品总销量)

效果:商品详情查询从 3 表 JOIN 变 1 表 SELECT,响应时间从 200ms 降到 20ms(10x 提升)。


六、2024+ 视角

6.1 NewSQL 时代反规范化的"自动化"

传统关系数据库需要手动反规范化;现代 NewSQL 自动管理数据分布

数据库反规范化自动化机制
TiDB分布式 JOIN 优化器、HTAP 自动分流
OceanBase全局索引、自动负载均衡
CockroachDB分布式 SQL、存算分离
PolarDB共享存储、Parallel Query
YugabyteDB文档 + SQL 混合

趋势:业务层不再需要反规范化,数据库自己解决 JOIN 性能问题

6.2 反规范化的"反模式"

常见错误

  1. 过度反规范化:所有表都冗余 → 维护成本爆炸
  2. 同步链路过长:A → B → C → D 链式同步 → 一处失败全错
  3. 无监控:不一致问题没人发现 → 业务数据错乱
  4. 无降级:同步服务挂了 → 业务瘫痪

正确做法

  • 只对热点查询路径反规范化
  • 同步链路尽量短(最多 1 跳)
  • 监控告警(不一致检测)
  • 同步服务降级开关(允许短暂不一致)

6.3 反规范化与 CQRS / Event Sourcing

  • CQRS(读写分离):写库规范化、读库反规范化 → 专门为查询优化
  • Event Sourcing:用事件流重建读库 → 天然解决一致性问题
  • CDC(Change Data Capture):用 Canal/Debezium 监听 binlog → 异步同步到读库

架构演进:单体反规范化 → 主从分离 → CQRS → Event Sourcing + CDC


七、答题套路总结

题型套路
列举反规范化方法答 4 种:增加冗余列 / 派生列 / 重新组表 / 分割表
选择哪种方法看场景:列表展示 → 冗余列;统计 → 派生列;冷热 → 分割表
解决不一致答 3 种:触发器 / 应用层代码 / 批处理
选择哪种同步强实时 → 触发器;可控 → 应用层;允许延迟 → 批处理

参考资料

  • 2021 年下半年系统架构设计师考试真题(下午案例分析 试题三 Q1、Q2)
  • 《系统架构设计师教程》第 8 章 数据库设计
  • 《数据库系统概念》(Database System Concepts)Silberschatz 等
  • 《高性能 MySQL》Baron Schwartz 等
  • 《数据密集型应用系统设计》(DDIA)Martin Kleppmann
  • TiDB / OceanBase 官方文档
使用 Hugo 构建
主题 StackJimmy 设计