Administrator
Administrator
发布于 2026-10-27 / 0 阅读
0
0

数据库入门到初级(13):从一个需求开始,怎么设计数据库表

数据库入门到初级 · 第 13/22 篇。实验口径:MySQL 8.4、InnoDB、utf8mb4。案例:小型商城。

学会 SQL 后,一个新问题出现了:面对一句“做个商城”,应该创建哪些表?表设计没有一份可以覆盖所有业务的万能答案,但有一套很实用的推导方法:从业务对象和业务事实出发,把含义、关系和规则逐步写清楚。

1. 先把需求写成可以讨论的句子

本系列的需求范围是:用户可以购买商品;一张订单属于一名用户;一张订单可以包含多个商品;明细记录成交时的名称、价格和数量;订单状态包括待支付、已支付、已取消。

暂不处理优惠券、退款、运费、地址、多规格和多币种。限定范围不是省略思考,而是避免在需求未明确时提前堆满字段。后续增加功能时,依据新的事实扩展模型。

2. 识别实体与业务事实

表 一行表达什么 主要字段
users 一名用户 编号、姓名、邮箱
products 一件可销售商品 编号、名称、当前价、库存
orders 一次订单 编号、所属用户、状态、总额
order_items 某订单中的一个商品项 订单、商品、成交快照、数量

“一行表达什么”叫业务粒度。粒度一旦不清楚,统计就容易重复:用户一行与用户每张订单一行,是完全不同的数据单位。

商城实体关系图

用户到订单、订单到明细、商品到明细都是一对多。订单与商品之间则通过明细形成多对多关系。图中的“1”和“多”说明数量关系,具体是否允许 0 条,仍需业务定义:用户可以没有订单,未完成创建的订单是否允许没有明细,需要额外规则。

3. 把业务规则映射成约束

“邮箱不能重复”对应唯一约束;“数量至少为 1”对应检查约束;“订单所属用户必须存在”对应外键;“总额不能为空”对应非空约束。

但不是所有规则都能用一个字段约束表达。例如“订单总额等于全部明细金额之和”,涉及跨行、跨表计算。本教程由下单事务计算和写入,并通过核对查询检查,不声称一个普通 CHECK 就能自动完成。

SELECT o.id, o.total_amount,
       SUM(i.unit_price * i.quantity) AS calculated_amount
FROM orders AS o
JOIN order_items AS i ON i.order_id = o.id
GROUP BY o.id, o.total_amount
HAVING o.total_amount <> SUM(i.unit_price * i.quantity);

初始样例应返回 0 行,表示已有明细的订单没有金额不一致。它不会发现完全没有明细的订单,要覆盖那种情况,还需要单独检查,或改为左连接并定义无明细订单的处理方式。

4. 当前数据与历史数据分别存在哪里

商品表保存当前名称和当前标价;明细表保存下单时的名称和成交单价。如果机械键盘以后涨到 219 元,订单 1001 仍应按 199 元计算。

这说明“同名字段出现两次”不一定就是坏设计。先判断它们代表的是同一事实,还是不同时间点的事实。第 15 篇会用范式和快照继续解释。

5. 用主要查询反向检查结构

设计完成后,至少试写几类查询:按用户查订单、按订单查明细、查可售且有库存商品、统计已支付金额。如果每个简单需求都需要拆解一串逗号或扫描大段 JSON,可能说明模型与业务访问方式不匹配。

还要问:删除用户是否会丢订单?商品改名是否改变历史记录?订单重复提交如何识别?本教程只实现了最小模型,真实系统通常还要增加业务订单号、幂等键、审计记录等。

6. 一份够用的数据字典

字段除了类型,还应记录含义、单位、是否可空、默认值和允许值。例如 total_amount 单位是元,当前教程不含退款和折扣;created_at 遵守统一时区约定;status 的英文值不能随意混用。

数据字典不是额外负担。它能减少“这个金额到底有没有扣优惠”“这个时间是服务器时间还是用户时间”等反复争论。

练习与答案

练习:如果同一用户可以收藏多个商品,同一商品也能被多人收藏,该如何设计? 新建收藏中间表,保存 user_id、product_id、created_at,并对用户和商品组合加唯一约束。不要给商品增加 favorite_user_ids 字符串。

建模的目标是让数据含义稳定、规则清楚、常见操作容易表达,而不是让表数量尽可能少。


上一篇:第 12 篇 · 系列目录 · 下一篇:第 14 篇


评论