物理模型设计需兼顾业务主键与代理键冲突、跨模块关联约束、分区索引匹配查询、DDL变更管控四方面,核心是平衡查询路径、写入吞吐与运维成本。
物理模型设计前必须明确业务主键和自然键冲突点
逻辑模型里的“用户id”在物理实现中常被替换成自增id,但下游报表、etl 或历史数据比对可能强依赖原始业务编码(如user_code)。不提前识别这类冲突,上线后就会出现关联断裂或去重异常。
实操建议:
- 逐表检查逻辑模型中标注为“业务主键”的字段,确认其是否具备唯一性、不可变性、非空性;若任意一条不满足,就必须在物理模型中保留该字段,并加
UNIQUE约束,而非仅依赖代理键 - 对含多套编码体系的表(如同时存在
erp_user_id、crm_account_no),物理建模时统一用business_key作为冗余字段,类型设为VARCHAR(64),避免后期拼接或转换 - 禁止在物理层直接删减逻辑模型中的候选键——哪怕当前没用,也可能是未来分区、物化视图或CDC同步的关键依据
模块化拆分物理表时如何控制跨模块外键引用
把“订单中心”“库存中心”“会员中心”拆成独立数据库后,order表里想引用member信息,不能直接建FOREIGN KEY——跨库外键在MySQL 8.0+仍不支持,PostgreSQL需借助postgres_fdw且性能敏感。
实操建议:
- 所有跨模块关联字段,统一定义为
CHAR(32)或VARCHAR(32),存储UUID格式的业务标识(如member_id),不存数字ID;这样既规避了主键类型不一致问题,又便于后期做逻辑复制 - 外键约束改由应用层校验 + 定期离线一致性扫描(例如用
pt-table-checksum跑跨库比对),而非依赖数据库强制约束 - 模块间高频查询场景(如订单页展示会员等级),优先通过异步双写或CDC订阅,在本地库冗余必要字段(如
order.member_tier),而不是每次JOIN远程表
分区策略与索引设计必须匹配查询模式,而非照搬逻辑实体粒度
逻辑模型里一个“交易流水”实体,在物理层如果按created_at月分区,但90%查询条件是status = 'success' AND merchant_id = ?,那分区几乎无效,还会拖慢DDL和备份。
实操建议:
- 先统计近3个月慢查日志中
WHERE子句高频组合字段,再决定分区键:高基数且范围查询多的选RANGE,等值查询多的选LIST或HASH - 每个分区表必须有覆盖查询条件的联合索引,例如按
merchant_id分区,则索引至少包含(merchant_id, status, created_at),否则分区裁剪后仍要全分区扫 - 避免对含JSON字段的表做RANGE分区——MySQL 5.7+虽支持JSON列,但分区函数不能引用JSON_EXTRACT结果,会导致
ERROR 1564 (HY000): This partition function is not allowed
模块间DDL变更如何避免物理模型漂移
开发A改了product表加了weight_unit字段,但运维B同步到生产环境时漏掉,或DBA手动执行SQL顺序错乱,导致同名模块在不同环境字段数/类型不一致——这种漂移不会报错,但应用读写会静默失败。
实操建议:
- 每个模块的DDL必须收敛到单个
schema.sql文件,由CI流水线自动校验MD5并执行;禁止人工拼接SQL或跳过某次版本 - 字段新增必须带默认值(如
weight_unit VARCHAR(10) DEFAULT 'kg'),避免ALTER TABLE ADD COLUMN阻塞大表导致线上超时 - 关键模块上线前,用
mysqldiff或pgdiff对比测试库与预发库的information_schema.columns,输出差异字段清单,人工确认后再放行
物理模型不是逻辑模型的镜像翻译,而是对查询路径、写入吞吐、运维成本三者的妥协结果。最容易被忽略的是:模块边界一旦划定,字段归属就再难调整——比如把“地址”从会员模块挪到订单模块,牵扯的不只是表结构,还有所有已部署的ETL脚本、BI取数口径、甚至前端埋点字段名。










