Skip to content

数据库三大范式

1NF:字段必须是原子的

第一范式要求列不能再拆。把「北京,朝阳」塞进一个 address,按区筛选、按区统计都会变成字符串解析,索引也帮不上忙。省、市、区、详细地址拆开后,每一列都是独立的过滤条件。

原子性是相对业务的。身份证号通常整列保存,不必拆成地区码和序号;一旦业务真的要按地区码检索,再拆才有意义。

2NF:非主键列必须依赖整个主键

第二范式建立在 1NF 之上:非主键列要完全依赖主键,不能只依赖复合主键的一部分。

订单明细如果以 (order_id, sku_id) 做主键,user_nameorder_time 只由 order_id 决定,和买了哪件 SKU 无关。这些列放在明细表里会出现:

  • 同一订单多行重复用户名,浪费空间
  • 改用户名要改多行,漏改就会不一致

正确做法是用户名、下单时间留在订单表,明细表只保留真正依赖 (order_id, sku_id) 的字段,例如 quantitysku_price

单列主键的表天然满足 2NF,部分依赖主要出现在复合主键上。

3NF:去掉传递依赖

第三范式要求非主键列之间不能互相决定。员工表里同时存 dept_iddept_name,部门名由部门 ID 决定,不由员工 ID 决定。部门改名时,所有员工行都要跟着改,这就是传递依赖带来的更新异常。

部门名放到部门表,员工表只留 dept_id。需要展示时再 JOIN,或在读多写少的列表页做有控制的冗余。

何时反范式

范式减少冗余和更新异常,代价是查询要 JOIN。列表页每次都联三张表拿用户名、商品名,延迟和复杂度都会上去。

常见折中是把「几乎不改、读极热」的字段冗余过来,例如订单表存下单时的 user_namesku_name 快照。这是用空间和一点一致性成本换查询,需要在写入路径同步更新,而不是随手复制一列就算完。