1. 为什么逻辑模型决定了数据库项目的大半成败
先说一个有点扎心的结论:很多数据库设计翻车,不是栽在SQL写不好、也不是服务器配置不给力,而是栽在逻辑模型阶段没有想清楚。我在实际项目里见过太多次——应用代码改了一茬又一茬,唯独表结构像打补丁一样越叠越厚,最后谁都不敢动。究其根源,基本都是逻辑模型这一步偷了懒。
逻辑模型是什么?它是介于业务需求和物理实现之间的那层"通用语言"。业务上描述"用户下单、订单包含多个商品",逻辑模型就把它变成"客户实体、订单实体、商品实体,客户与订单是1:N关系,订单与商品是M:N关系"。它不关心数据最终存在MySQL还是Oracle里,也不关心用不用索引、怎么分表,只关心业务世界里的概念、规则和约束。
为什么要单独做这一层,而不是画完概念图直接建表?因为逻辑模型是所有后续工作的契约。程序员按它写业务代码,DBA按它设计物理存储,产品经理按它确认需求边界。如果这层契约是模糊的、有歧义的,下游所有环节都会拿各自的理解去实现,最后表结构、接口文档、代码逻辑三套说法,排查问题的时候你就知道什么叫痛苦。
还有一个经常被忽略的点:逻辑模型的修改成本,远高于代码的修改成本。代码错了改个方法、加个判断,影响范围是局部的;表结构错了要改字段、改数据、改索引、改接口、改缓存,一条链全要跟着动。尤其是上线之后已经有存量数据,改一张核心表的代价,往往能让一个小迭代变成一个"需要停服迁移"的大工程。所以在这个阶段多花几天,把模型磨清楚,是在给整个项目省钱。
这篇文章,我会按照自己实际做设计时的思路,把逻辑模型建模的核心要点、数据库系统架构的分层逻辑、存储结构的底层机制,以及从逻辑模型到物理存储的完整映射串起来讲一遍。内容偏基础但没有一句废话,适合刚接触数据库设计的人,也适合已经写过不少表、但想系统梳理一遍设计方法论的人。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 逻辑模型的建模核心:实体、属性、关系怎么落笔
2.1 实体识别:从业务描述里捞概念,而不是捞名词
实体(Entity)是逻辑模型的基本单元。但很多新手容易犯一个错误:把业务描述里出现的所有名词都当成实体。比如"用户点击购买按钮,系统生成订单,订单包含商品,商品属于分类",这里"按钮""系统"显然不是实体,它们是动作载体或系统组件,不是我们需要持久化描述的业务概念。
我个人的经验是,判断一个词是不是实体,就问自己三个问题:
- 它有没有一组需要单独维护的、稳定的属性?比如"用户"有姓名、手机号、注册时间,明显需要;"按钮"没有属性需要维护,排除。
- 它在业务里是独立存在的概念,还是依附于另一个概念的描述?比如"订单明细"虽然离不开订单,但它本身承载数量、单价这些属性,有独立存在的必要。
- 业务规则里是否需要单独追踪它?比如"库存",它既可以是商品的属性,也可以是一个实体,取决于你是只查当前库存量,还是需要记录库存变动的流水。要记录流水,就必须拆成实体。
还有一种情况是"弱实体"(Weak Entity)。它自己没有完整的主键,必须依赖父实体才能唯一标识。最典型的例子是订单明细:同一张订单下,明细可以有"明细编号"来区分,但如果没有订单号,这个明细编号没有任何全局意义。弱实体在逻辑模型里要用双线矩形表示,转换成关系模式时,它的主键是"父实体主键 + 局部标识"的复合主键。这一步处理不好,后面建表的时候主键设计就会纠结。
2.2 关系的维度:基数定对,模型就稳了一半
实体之间关系的核心是基数(Cardinality)和可选性(Optionality),这两者定义业务规则约束。
基数解决"一个A对应几个B"的问题,种类就三种:
- 1:1:一个A最多对应一个B,一个B最多对应一个A。比如"用户"和"用户详情"。
- 1:N:一个A对应多个B,一个B只属于一个A。比如"分类"和"商品"。
- M:N:一个A对应多个B,一个B也对应多个A。比如"订单"和"商品",一张订单有多个商品,一个商品也出现在多张订单里。
可选性解决"是否允许不出现"的问题:是强制(Must)还是可选(May)。比如"订单"和"收货地址",下单必须填地址,这是强制;"用户"和"优惠券",用户可以不领券,这是可选。
基数定错的后果非常典型。我见过一个会员系统的设计,把"用户"和"角色"设计成1:1,理由是"一个用户只有一个角色"。后来业务要加"一个用户可以既是运营又是推广",表结构只能硬改,加关联表,数据还得清洗。如果当初建模时多问一句"未来会不会有多个角色",这个问题根本不会发生。设计时永远假设业务复杂度会上升,能拆M:N就尽量不要硬塞成1:N或1:1。
2.3 关系的转换规则:从ER图到关系模式的固定套路
逻辑模型最终要转成关系模式(也就是表结构),转换有一套约定俗成的规则,掌握了就不容易出错:
- 每个实体转成一张表,实体属性就是表字段。
- 1:1关系
