先判断一件事:现有数据能不能通过“派生”得到你缺的信息。如果能,优先加计算字段或视图,不动原表;如果不能,才走结构变更。两条路的代价差一个数量级,判断依据是缺的字段是否依赖已有字段之间的运算、拼接或条件映射。
典型情况是当初只存了下单时间和发货时间,现在报表要“履约天数”;或者只存了省份,现在要按大区汇总。这类需求不需要新增存储列,加一个计算字段、数据库视图或查询里的表达式即可。
实施动作:先在只读副本或本地导出的一份数据上写查询,确认计算结果与业务口径一致,再决定把它固化成视图还是每次查询时现算。如果这个字段会被高频筛选或排序,视图可能变慢,此时才考虑物化。
结果如何影响下一步:如果派生方案能覆盖八成以上的新需求,就把它作为默认路径,只在真正需要独立存储时才改表。反过来,如果发现要拼接三四个字段、还要处理大量例外,说明业务概念已经独立,继续硬算会让查询越来越难维护,应转向结构变更。
比如原来用户表只有一列联系方式,现在要区分手机、邮箱、社交账号并分别验证;或者原来一个订单只对应一个收货地址,现在要支持多地址。这类是语义变化,不是计算问题。
扩展顺序建议如下,从影响最小到最大:
每做一步都要问:现在还有哪些代码在读写旧字段?如果无法确定,就先不要删旧字段,只做加法。删列是不可逆动作,而加列基本可以回退。
很多情况下你拿不到生产库的写权限,或者历史数据本身残缺。此时仍可执行的最小动作是:导出一份脱敏样本,在本地或测试环境按目标结构建表,把样本灌进去,跑一遍真实查询,看结果是否符合预期。
这个动作能回答的问题:新结构是否支持你要的筛选、排序和聚合;迁移脚本在遇到空值、重复值、超长文本时会不会报错。
它不能推出的结论:不能证明生产环境迁移的耗时,不能证明线上写入并发下不会锁表,也不能证明所有历史数据都能被正确映射。样本里没有的边界情况,只有到真实数据上才会暴露。
假设你自建的商品表只有价格一列,现在要支持“原价 + 促销价 + 促销起止时间”。
区分点在于:这个值是否需要“当时是什么就是什么”。需要留痕的,就要独立存储;只是当下展示的,可以派生。
改完结构后,至少做两件事:用旧数据跑一遍新查询,确认没有因空值或类型转换产生错误结果;再模拟一次新数据写入,确认必填项和默认值符合预期。
例外情况:如果这张表还被外部系统直接读取,改列名或改类型会同时破坏对方,此时应新增列并保留旧列,等对方切换后再清理。如果数据量很大,一次性回填可能造成长时间锁表,应分批处理并记录进度,而不是追求一次跑完。
判断扩展是否成功的标准不是“字段加上了”,而是新查询能稳定返回结果,且旧功能没有因此报错。这两点都验证过,再考虑清理过渡字段。