|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?用户注册
×
数据库范式设计与反范式权衡
一、具体的问题
很多团队在初期为了"快",把一张表里塞进客户名、客户地址、订单商品、订单数量、订单金额,所有字段平铺成一个大宽表。上线半年后开始出问题:改一次客户地址要把几百条历史订单全部 UPDATE 一遍;某客户有两个地址时表里出现重复行,统计销售额就翻倍算错;删除某个客户的最后一条订单时,连客户的基本信息也一起没了。这些现象都有一个共同的名字——更新异常、插入异常、删除异常,根子都在"表设计没有遵循范式"。本文把范式是什么、该用到第几范、什么时候要故意打破它,一次说清。
二、核心原理
1. 三范式到底在约束什么
- 第一范式(1NF):每一列都不可再分,只存原子值。比如"联系电话"列里写"138xxxx,139xxxx"就算违反 1NF,应该拆成独立行或独立表。
- 第二范式(2NF):在满足 1NF 的基础上,非主键列必须完全依赖整个主键,不能只依赖主键的一部分。它只针对"复合主键"场景:若主键是(订单号, 商品号),而"客户姓名"只依赖"订单号"这一个字段,就出现了部分依赖,应把客户信息拆出去。
- 第三范式(3NF):非主键列不能传递依赖于主键。典型例子:订单表里既有"客户编号"又有"客户所在城市",城市是通过客户编号推导出来的,改了客户城市就得改所有订单——这就是传递依赖,城市应放到客户表里。
BCNF 是 3NF 的加强版,主要处理"复合候选键之间相互决定"的少数情况,日常业务里 3NF 基本够用。
2. 反范式(Denormalization)不是错误,是权衡
范式越高,冗余越少、写入越干净,但查询越要连表。当一张核心报表每天被查询几万次、而写入一天只有几百次时,死守 3NF 会让每次查询都背上三张表的 JOIN 开销。此时把"客户城市""商品名称"冗余进订单宽表,用多一次写入成本换查询免 JOIN,是工程上合理的选择。关键是:冗余字段必须是"读多写少、变化慢"的,且要有机制在源数据变更时同步更新,否则冗余就会变成错误来源。
三、实例参考(动手步骤)
下面用"客户—订单—订单明细"这个经典场景,演示从扁平大表到规范设计的改造,并给出一种常见的反范式宽表。
1) 规范化拆分(消除部分依赖与传递依赖):
- CREATE TABLE customer (
- cust_id INT PRIMARY KEY,
- cust_name VARCHAR(50),
- city VARCHAR(50)
- );
- CREATE TABLE orders (
- order_id INT PRIMARY KEY,
- cust_id INT,
- order_date DATE,
- FOREIGN KEY (cust_id) REFERENCES customer(cust_id)
- );
- CREATE TABLE order_item (
- order_id INT,
- product_id INT,
- qty INT,
- price DECIMAL(10,2),
- PRIMARY KEY (order_id, product_id)
- );
复制代码
这样改客户城市只动 customer 一行;删订单不影响客户信息;每个商品一行,统计不会翻倍。
2) 为高频报表做反范式宽表(读多写少场景):
- CREATE TABLE order_report_wide (
- order_id INT,
- cust_name VARCHAR(50),
- city VARCHAR(50),
- product_id INT,
- qty INT,
- price DECIMAL(10,2)
- );
复制代码
3) 前后对比:规范设计下查"某城市销售额"要 JOIN customer;宽表下直接 SELECT city, SUM(qty*price) FROM order_report_wide GROUP BY city,少了两表 JOIN。代价是写入订单时要同步维护宽表——可用触发器或定时任务刷新,并只对"已支付、只读"的历史订单冗余,活跃订单仍走规范表。
四、实操检查清单
- 新表立项时先按 3NF 设计,把"一个字段依赖谁"逐列问一遍,标出部分依赖与传递依赖并拆表。
- 判断一张表要不要反范式,先看读写比:读远多于写、且冗余字段变化慢,才值得冗余。
- 冗余字段必须配同步机制(触发器 / 落库后刷新 / ETL),并写明"以哪张表为准",避免双写不一致。
- 宽表只承载"稳定历史"数据,活跃写入路径仍走规范表,降低同步复杂度。
- 用 EXPLAIN 对比规范 JOIN 与宽表直查的执行计划与逻辑读,用数据决定取舍,不要凭感觉。
- 定期复查宽表与源表的差异行数,差异持续增大说明同步链路已坏,需告警。
- 团队内约定一份"表设计评审清单",每张核心表上线前过一遍范式与冗余策略,避免事后返工。
|
|