建模先于建表

很多表结构是边写边改出来的:功能用到哪张表就加哪张,缺字段就加字段,结果几年下来表里堆满意义不明的列、到处是重复数据、外键关系只有老员工才说得清。

数据库建模的意义在于先把业务的形状想清楚,再落到表结构上。它关心三件事:系统里有哪些业务对象,对象之间是什么关系,每个对象有哪些关键属性。想清楚这三件事,建表只是最后的抄写工作。

表结构是业务的骨架:骨架歪了,后面每一个功能都长不直。
建模的四步走
  • 找实体把业务对象圈出来,客户、订单、商品。
  • 定关系一对多、多对多、一对一各归各位。
  • 列属性每个对象的关键字段和约束。
  • 验需求用核心查询走一遍,模型是否接得住。
数据库建模示意:蓝色渐变背景中央发光数据立方体,四周环绕多个发光实体卡片方块并由发光关系线相连,底部叠放发光表格条带,一道发光迁移箭头从左侧旧表指向右侧新表,扁平科技插画
实体、关系、属性,三件事理清了,表结构自然立得住。

实体关系:从业务对象到模型

实体关系建模(ER 建模)是最常用的建模语言。先从业务描述里挑出名词——它们多半是实体;再挑出动词——它们多半是关系。比如「一个客户可以下多个订单,一个订单包含多个商品」:客户、订单、商品是实体,下订单和包含是关系。

# 一张订单场景的实体清单(示意)
客户(customer): id, name, phone, level
订单(order):    id, customer_id, status, total, created_at
订单明细(order_item): id, order_id, product_id, qty, price
商品(product):  id, name, sku, price, stock
# 关系
客户 1 ── n 订单
订单 1 ── n 订单明细
商品 1 ── n 订单明细

关系映射到表有两个要点:一对多在「多」的一方加外键;多对多需要中间表;一对一通常合并成一张表,除非是频繁读取的大字段拆分。

规范化:消除冗余的三范式

规范化是对冗余的系统性治理,三范式是绕不开的三个台阶:

范式要求解决的问题
第一范式字段不可再分,一列只存一个值逗号分隔、多值字段
第二范式非主键列完全依赖主键部分依赖导致的重复
第三范式非主键列之间不互相依赖传递依赖导致的更新异常

到第三范式,冗余基本被清干净:一份数据只在一个地方维护,改一处就全对。但规范化的代价是查询要多表关联,所以在真实系统里,第三范式是起点而不是终点。

# 违反第三范式的例子:部门名依赖部门编号
employee(id, dept_id, dept_name)
# 部门改名时所有员工行的 dept_name 都要跟着改,漏一处就错一处
# 规范做法:员工表只留 dept_id,部门名由部门表维护

判断要不要继续规范化的标准只有一个:这份冗余会不会引发更新异常。不会引起一致性问题的冗余,可以留着。

反规范化:查询优先的取舍

规范化的反面不是不规范化,而是有意识地反规范化:在明确知道代价的前提下,用少量冗余换查询性能或实现简单。典型场景是高频读取、很少更新的数据。

  • 汇总列:订单表存一个订单总额,而不是每次从明细实时求和。
  • 冗余字段:列表页要显示用户名,就在订单表冗余一份,省掉每次联表。
  • 预计算:统计报表落成快照表,避免大表实时聚合。
反规范化是拿一致性成本换性能收益,前提是你要承担维护冗余的职责。

反规范化的风险在于来源不一致:冗余列和源表数据不同步。应对方法是把冗余的维护点收敛到一处,比如由同一段服务代码负责写入,并加数据一致性校验。