本节摘要:逻辑设计决定数据怎么组织。本节讲清楚范式与反范式权衡、字段类型选择、主键设计、外键管理,让你设计出性能与规范兼顾的表结构。
范式(Normal Form):消除数据冗余,保证一致性。
范式好处:冗余少、更新一致、空间省。坏处:关联多、JOIN 多、查询慢。
反范式:为查询性能冗余字段,违反范式。
反范式好处:查询快(少 JOIN)。坏处:冗余、更新多处、一致性维护成本。
权衡:OLTP 高频小查询,适度反范式(冗余稳定字段);OLAP 分析查询,重度反范式(宽表)。不要为范式牺牲性能,也不要为性能牺牲一致性——按场景权衡。
类型影响存储、计算、索引性能:
1. 整数 vs 字符串
2. 定长 vs 变长
3. 日期时间类型
4. DECIMAL vs FLOAT
5. TEXT/BLOB
6. ENUM
主键影响索引、JOIN、复制性能:
1. 自增整数
2. UUID
3. 雪花 ID
4. 复合主键
原则:单机用自增整数,分布式用雪花/顺序 UUID,避免随机 UUID 主键。
外键:保证参照完整性,但影响性能:
权衡:
替代:应用层校验 + 唯一约束 + 逻辑外键(无约束但逻辑关联)。
1. 小表原则
2. 冷热分离
3. 适度冗余
4. 预留扩展
⚠️ 常见误读:以为"严格范式最好"。范式保证一致性但 JOIN 多查询慢。OLAP 重度反范式(宽表),OLTP 适度反范式(冗余稳定字段),按场景权衡。
💡 关键直觉:逻辑设计——范式(1NF/2NF/3NF/BCNF 消冗余保一致)vs 反范式(冗余字段/汇总/快照提查询),权衡(OLTP 适度反范式稳定字段、OLAP 重度反范式宽表)。字段类型——整数比字符串快(自增 ID 而非 UUID 字符串,UUID 用 BINARY(16))、CHAR 定长/VARCHAR 变长、TIMESTAMP(4字节1970-2038)/DATETIME(8字节)、DECIMAL 金额(不用 FLOAT)、TEXT/BLOB 不索引分离存储、ENUM 省空间但不灵活。主键——自增整数(顺序写快索引小)、UUID(随机写慢索引大,用顺序 UUIDv7/ULID)、雪花(分布式趋势递增)、复合主键(通常代理键+唯一约束)。外键——OLTP 用(强一致)、高并发/分布式不用(应用层校验)、OLAP 不用。表原则——小表(千万分区亿分片)、冷热分离、适度冗余稳定字段、预留扩展避免频繁 ALTER。
逻辑设计的微观决策里,字段类型是最容易被轻视的一项,给一份隐性成本清单。过宽的主键:主键会被复制进每个二级索引,字符串主键比整型主键让所有索引体积膨胀数倍,索引内存效率与扫描速度全线下滑;分布式场景需要全局 ID 时,有序的整型族方案优于随机字符串。可空字段的坑:空值在索引与统计里的行为与直觉不同(部分优化器对空值列的估算偏差),非必要不设空、用默认值与哨兵值替代。浮点与定点:金额用浮点是事故预定(精度丢失的对账灾难),定点数或以最小单位存整数是铁律。超长字段:行溢出到独立页意味着一次行读取变两次 IO,大文本要么压缩、要么分表(主表瘦身上策)。枚举与字典表:枚举改动要改表结构,字典表只插数据,高频变更的枚举用字典表更稳。这份清单的每一行都对应着真实的故障复盘——类型选对的收益是隐性的(什么都没发生),选错的代价是显性的(半夜的告警电话),权衡的天平从一开始就倾向认真的人。
逻辑设计的收官谈主键——它比看起来深。业务主键 vs 代理主键:用业务字段(身份证号、订单号)当主键,业务规则变更时(订单号格式调整)就是数据库的地动山摇;代理主键(自增或全局 ID)与业务解耦,代价是多一列索引空间——主流实践是代理主键加业务唯一索引,两全。分布式环境的主键:单库自增在分库后冲突,常用解法是号段分配(每次取一段)或雪花族算法(时间戳加机器加序列);雪花族的隐性坑是时钟回拨(生成重复或错乱 ID),部署时要配时钟守护,这个坑踩过的人不多但都刻骨。主键与插入模式:有序主键让插入永远发生在 B 树最右侧(页顺序写、缓存友好),随机主键(如无序哈希)让插入散布全树(页分裂频繁)——第 2 节的页物理学在这里落地为选型建议。三条深水区合起来的选型决策树:单库小系统用自增足矣;分库系统按运维能力选号段或雪花;无论哪种,业务字段一律另建唯一索引。主键是表的心脏,值得设计会上最认真的十分钟。
逻辑设计的知识浓缩成一张评审检查表,新表上线前逐项过。主键与标识:代理主键还是业务主键,分布式环境 ID 方案与时钟依赖确认。字段类型:金额定点化、时间统一时区、状态列有无枚举值文档、大字段是否分表。约束完整性:非空与默认值齐备、外键策略(物理外键还是应用层维护)与团队规范一致、唯一约束覆盖业务不变量(业务上的唯一就该有唯一索引兜底)。命名与文档:表与列的命名风格统一、每张表有注释说明用途与负责人、枚举值有字典。预留设计:软删除与审计字段(创建更新时间、操作人)是否按团队标准带上——它们不影响今天的性能,但影响半年后每一次排查的速度。检查表的价值不是形式主义,是"设计的隐含假设显性化"——评审桌上多问十分钟,上线后就少一次"这个字段当时为什么这么设计"的考古。
逻辑章收官做一个"范式与业务"的翻译练习——设计能力的最终形态是把业务语言实时翻译成结构约束。业务说"一个用户可以有多个收货地址,但只能有一个默认地址"——翻译成结构:地址表带用户外键加默认标志,加"每用户仅一条默认"的约束(部分库的表达式唯一索引,或应用层加事务保证)。业务说"订单金额以下单时为准,商品调价不影响历史订单"——翻译:订单表存金额快照而非引用商品表价格——这不是反范式,是业务语义的正确建模(快照字段本就该固化)。业务说"支持用户注销后数据保留一年再删除"——翻译:软删除标志加计划任务,删除路径要覆盖所有关联表(外键的级联策略此时要显式设计)。三个翻译例子的共性:好的逻辑设计不是范式的机械应用,是业务规则的忠实映射——每个业务不变量都要在结构里找到承载(约束、快照、标志位),找不到承载的规则将来必然靠人肉补丁维护,而人肉是会离职的。
收尾一句:逻辑设计的所有技巧,最后都汇成"让结构替人记住规则"这一件事——约束记不变量、快照记历史语义、类型记边界;结构没记住的规则,最终都会变成某次深夜故障复盘里的"当时怎么没考虑到"。设计每一列时多问一句"这个业务规则靠什么承载",就是最好的设计习惯。