从业务需求到数据模型:实体、关系与拆表
1435597771 ·
在本篇文章中,我们只解决一个问题:一段业务需求,到底应该变成那些表?
数据库设计中的很多问题,在建表初期就已经埋下了隐患。业务后期如果要对前期草率的设计做改动,成本极高——查询会越来越扭曲,数据不一致的问题也会频发。
正确的路径其实很清晰:先从业务中识别实体、属性和关系,再决定表结构、外键位置和是否需要中间表。
1.为什么不能拿到需求就直接建表
我们需要先达成一个共识,需求往往是模糊的,比如这句话:”一个订单可以包含多个商品,每个商品有数量和单价“, 如果我们不分析这个需求直接建表,那么很容易犯下三类错误:
把可变数量的东西塞进固定字段
错误做法:在订单表里直接加 product1_id、product2_id、product3_id
问题:商品数量一旦超过设计时的上限就崩了,而且查询和统计极其痛苦。
把本来独立生命周期的对象当作附属字段
错误做法:把商品名称、价格直接写死在订单表,而不独立建表
问题:商品信息修改,那么历史订单也会被错误的修改
把多对多关系错误地用1:N或字段列表实现
错误做法:在订单表用一个 product_ids 字段存逗号分隔的商品ID列表
问题:无法正确维护数量、单价等关系属性,也无法利用数据库的外键约束。
直接建表的本质问题是:跳过了概念建模这一步。概念模型回答的就是:“在业务中有哪些实体,它们如何关联”。而表只是概念模型的实现,如果没有这一步,表结构会跟着业务的表面描述走,而不是根据业务本质走。
2.什么是实体、属性、关系
直接建表的本质问题是跳过了概念建模。那么概念建模是什么?
概念建模就是由实体、属性、关系这三大基本要素组合而成的。因此进入概念建模之前,必须要对这三个概念建立清晰、准确的理解。否则在后续的建模过程中很容易凭感觉画图或者直接翻译需求句子,容易浮于表面。下面是这三个基本要素的基本概念:
实体(Entity):业务中可以独立识别、独立存在的事物。通常对应对应一张表。例如:用户、商品、订单
属性(Attribute):描述实体或属性的特征,通常对应表中的一列。例如:用户的姓名、手机号;商品的价格、库存
关系(Relationship): 实体之间的关联。 我们需要注意的是,关系本身也可以有属性。例如订单“包含”商品
如何判断一个概念到底是实体还是属性?
关键看它是否满足两个条件:
可独立识别(有自己的业务标识)
有独立生命周期或可被独立操作
简单口诀:“能不能单独拿出来管理?能不能单独对它增删改查?”
如果答案是“能”,它更可能是实体;如果只是某个实体的附属描述,那它就是属性。
3.怎么判断1:1,1:N,N:N
理解了实体、属性和关系之后,下一步就要判断实体之间的关系基数(1:1、1:N、N:N)。
那么如何判断关系呢?
判断关系的黄金问题就是:从一个实体的实例出发,另一边最多能关联多少个实例。然后反过来再问一次。下面是不同的关系说明:
1:1关系 :两边都是最多一个,比如用户 与用户详细资料
1:N(一对多)关系:一边最多一个,另一边可以多个。比如:用户1:N订单(一个用户有多个订单,但是订单只能属于一个用户)
N:N(多对多)关系:两边都可以有多个。比如:学生和课程(一个学生可以上多个课程,同时一个课程也会属于多个学生)
4. 1:N为什么外键放在N侧
在1:N关系中,外键必须放在“N”的那一侧。原因很直观:
如果把外键放在“1”侧,一个字段无法存储多个值(除非用数组,但这会失去关系性数据库的约束)
把外键放在“N”侧,每个“多”的记录只指向一个“1”,天然满足基数约束。
5.N:N为什么需要关系表
我们在说1:N关系的时候说过,外键必须要放在“N”侧,因为在1侧无法用一个字段存储多个值。
到了在N:N关系中,任何一边放外键都无法正确存储多个值。这时的标准做法就是引入关系表(也叫做关联表、中间表)
关系表的特点:
至少包含两边的外键(通常做成联合主键或加唯一索引)
本身可以没有业务意义,只是链接两个实体。
比如学生和课程的N:N关系,需要student_course表,这个表中包含student_id+course_id
6.中间表什么时候回变成业务实体
中间表是描述两个实体之间的关系,但是关系本身也有它的属性,比如学生和课程这个关系中,选课时间就是关系的属性。 这个属性叫做关系属性。
当一个中间表拥有他自己的属性的时候,或者开始承载独立的业务操作时,那么这个时候中间表就不再是存粹的连接器,而是升级为业务实体。
最典型的例子就是电商中的「订单明细」:
订单和商品是 N:N 关系
但中间表需要记录数量、单价、小计等
因此 order_item 就成为了真正的业务实体,而不是单纯的中间表。
7. 完整示例
我们用这个业务需求来一步一步分析出需要有哪些表:
“用户可以下多个订单,每个订单可以包含多个商品,每个商品在订单中有数量和当时的成交单价。”
识别实体:用户(User)、订单(Order)、商品(Product)
判断关系:
用户 1:N 订单
订单 N:N 商品(因为一个订单多个商品,一个商品也可出现在多个订单)
因为“数量 + 成交单价”是关系属性,所以中间表升级为业务实体 OrderItem
最终表:
user
product
order(外键指向 user)
order_item(外键分别指向 order 和 product,并记录 quantity、price)
总结:从业务需求到表的决策路径
上面我们分别讲清了实体、属性、关系以及它们如何落地到表结构。现在把这些内容串成一条完整的决策路径:
先识别业务中的核心实体
区分哪些事真正的属性(固定、从属、无独立操作),哪些应该独立成表(数量可变、独立操作、独立声明周期)
画出实体之间的关系,区分为是什么关系
1:N外键放在N侧,N:N关系要引入关系表
当关系本身有属性或者业务意义时,把中间表升级为业务实体(OrderItem)
永远记住:查询习惯和SQL写法不决定关系类型,要从业务考虑。
当我们按照以上步骤思考,从一段业务出发,得到的就不再是"感觉有哪些表",而是一套有明确理由、可扩展、可维护的数据模型。无论是后续加字段、该关系还是优化查询,都有清晰的概念锚点。
以上就是"从业务需求到数据模型"的核心方法论:先想清楚世界有什么、它们如何关联、再决定表该怎么建。