建模先于建表
很多表结构是边写边改出来的:功能用到哪张表就加哪张,缺字段就加字段,结果几年下来表里堆满意义不明的列、到处是重复数据、外键关系只有老员工才说得清。
数据库建模的意义在于先把业务的形状想清楚,再落到表结构上。它关心三件事:系统里有哪些业务对象,对象之间是什么关系,每个对象有哪些关键属性。想清楚这三件事,建表只是最后的抄写工作。
表结构是业务的骨架:骨架歪了,后面每一个功能都长不直。
- 找实体把业务对象圈出来,客户、订单、商品。
- 定关系一对多、多对多、一对一各归各位。
- 列属性每个对象的关键字段和约束。
- 验需求用核心查询走一遍,模型是否接得住。
实体关系:从业务对象到模型
实体关系建模(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,部门名由部门表维护
判断要不要继续规范化的标准只有一个:这份冗余会不会引发更新异常。不会引起一致性问题的冗余,可以留着。
反规范化:查询优先的取舍
规范化的反面不是不规范化,而是有意识地反规范化:在明确知道代价的前提下,用少量冗余换查询性能或实现简单。典型场景是高频读取、很少更新的数据。
- 汇总列:订单表存一个订单总额,而不是每次从明细实时求和。
- 冗余字段:列表页要显示用户名,就在订单表冗余一份,省掉每次联表。
- 预计算:统计报表落成快照表,避免大表实时聚合。
反规范化是拿一致性成本换性能收益,前提是你要承担维护冗余的职责。
反规范化的风险在于来源不一致:冗余列和源表数据不同步。应对方法是把冗余的维护点收敛到一处,比如由同一段服务代码负责写入,并加数据一致性校验。
主键与索引设计
主键和索引决定了表好不好用。主键的选择常见有三种:
| 类型 | 优点 | 代价 |
|---|---|---|
| 自增整数 | 写入快、空间小 | 分布式下易冲突、会暴露业务量 |
| 雪花/有序 ID | 分布式可用、近似有序 | 实现稍复杂 |
| UUID | 全局唯一、不暴露信息 | 随机写入碎片化、索引偏大 |
索引设计把握三条原则:为查询建索引,把高频查询的过滤和排序字段纳入索引;控制索引数量,每个索引都是写放大,表上的索引不是越多越好;注意最左前缀,联合索引的字段顺序要按查询模式排。
-- 为高频查询建联合索引:按用户查、按时间排
CREATE INDEX idx_user_created ON orders (customer_id, created_at);
-- 避免无用的单列索引堆积,定期用慢查询日志反查
表结构演进:迁移与兼容
业务一定会变,表结构也一定会改。演进的关键不是不变,而是每次变更都可控、可回退。手段就是结构迁移:把每次表结构变更写成一份迁移脚本,按序执行、记录版本。
-- 一次加列迁移(示意)
ALTER TABLE orders ADD COLUMN channel varchar(16) NOT NULL DEFAULT 'web';
-- 先加可空/带默认值的列,再逐步填充数据、收紧约束
-- 让旧代码在列存在期间也能正常工作,实现平滑演进
- 先加列再改代码,顺序不能反,否则新旧代码同时在线时会报错。
- 大表变更要分批:加列用瞬时 DDL,加索引用在线方式,避免锁表。
- 每次迁移都要有回滚脚本,出问题能退回去。
数据迁移还要考虑兼容窗口:发布期间新旧代码并存,数据库必须同时支持两个版本的行为。
建模落地与评审
模型画得再好,不落到评审和执行就是纸上谈兵。落地时做三件事:
- 模型评审:上线前拉产品、开发、DBA 一起过一遍,重点核对关系是否真实、命名是否统一、约束是否齐全。
- 建表规范:统一的表名、字段名风格,时间戳字段、逻辑删除、版本号等约定提前定好。
- 查询验证:把核心业务查询在模型上走一遍,看是否要绕远路、有没有缺索引。
建模不是一次性的。业务每长出一块,模型就跟着演进一次,但要守住底线:关系不乱加、字段不乱增、约束不放松。表结构是业务的骨架,维护它就像维护代码一样需要纪律。