先给结论:不要急着在原来那张表上继续加列,也不要一上来就拆成新表。正确顺序是先确认“缺的字段”属于哪一类:是订单主表本来就该有的业务属性,还是某个环节的明细、快照或扩展属性。前者可以直接加列,后者应该新建关联表。判断依据不是字段数量,而是这个字段会不会出现一对多、会不会随时间变化、查询时是否需要独立筛选。
假设你手里是订单表,上线后发现需要记录“每个订单使用的优惠券编号”。如果一单只能用一张券,直接加一列 coupon_code 是合理的,改动小、查询直接。但如果一单可以叠加多张券,加列就会逼你写成 coupon_code_1、coupon_code_2,这是典型的错误信号。
更稳妥的做法是建一张订单优惠券关联表,字段包括订单ID、券ID、抵扣金额、使用时间。这样一张订单对应多行,后续要统计某张券的核销情况时也能直接按券ID筛选,而不需要解析拼接字符串。代价是查询时要多一次关联,列表页如果一次展示大量订单,需要控制关联条数或做汇总字段。
可执行动作:把候选字段逐个问三个问题——会不会一单多条、会不会被修改、是否需要单独统计。三个问题里有一个答案是“会”,就优先考虑新表;全部为“否”,才加列。
商城上线后数据仍在持续写入,扩展时不能只考虑结构,还要考虑新旧数据并存。推荐顺序是:先加可空列或新表,再让新代码同时写新旧结构,最后回填历史数据并切换读取。这样任何一步出问题都能回退,不会出现旧订单读不出来的情况。
假设你要给商品增加“规格重量”,用于计算运费。如果直接加一个非空列,历史商品没有值,写入就会失败。合理做法是先允许为空,新商品保存时写入,历史商品在编辑或批量处理时补齐;运费计算逻辑对空值走默认重量。这个默认值必须明确写进业务规则,否则同一个商品在不同订单里可能算出不同运费。
这里有一个容易忽略的取舍:回填历史数据时,是批量脚本一次跑完,还是按需在读取时补算。批量脚本结果一致、后续查询简单,但执行期间可能锁表或占用资源;按需补算对线上影响小,但每次读取都要判断空值,逻辑更复杂。数据量不大且可以选低峰执行时,优先批量;数据量大且不能停写时,先按需补算,再分批回填。
很多扩展需求其实不是字段不够,而是把过程数据当成了主表属性。例如订单金额,下单时的应付金额、支付时的实付金额、退款后的剩余金额,三者可能不同。如果只在订单表放一个金额字段,后续每次对账都会出问题。
更清晰的结构是:订单主表保留当前状态和汇总金额,另建金额变更记录表,记录每次变动的原因、金额和时间。查询当前值读主表,对账和追溯读记录表。这样新增一种优惠类型时,不需要再改订单表结构,只需要在记录表里增加一个变动类型。
判断标准可以更直接:这个字段的值会不会因为后续操作而改变?会改变,就考虑用记录表承载变化过程;不会改变,比如下单时的商品快照价格,就适合放在订单明细里固定下来。
完成这三项验证后,再决定是否把临时兼容逻辑清理掉。清理动作本身也要分批,先停止写入旧结构,观察一段时间确认没有遗漏读取,再删除旧列或旧表。跳过观察期直接删除,是上线后扩展数据字段最常见的返工原因。