回滚测试要确认的不是“能不能再部署旧版本”,而是旧版本能不能正确读写新版本留下的数据。这篇用真实数据库和 Java 序列化量出:哪些迁移旧代码扛得住,回滚迁移丢什么,以及滚动窗口里两个版本同时写会发生什么。
[M1 新增可空列 email] INSERT 成功;SELECT 成功;UPDATE 成功 [M2 新增 NOT NULL 列(表里有 2 行,无默认值)] 迁移本身失败:column "email" of relation "users" contains null values [M3 新增 NOT NULL 列(空表,无默认值)] INSERT 失败(null value in column "email" of relation "users" violates not-null constraint);SELECT 成功;UPDATE 成功 [M4 新增 NOT NULL 列并带默认值] INSERT 成功;SELECT 成功;UPDATE 成功 [M5 列改名 name → full_name] INSERT 失败(column "name" of relation "users" does not exist);SELECT 失败(column "name" does not exist);UPDATE 成功 [M6 删除列 age] INSERT 失败(column "age" of relation "users" does not exist);SELECT 失败(column "age" does not exist);UPDATE 失败(column "age" does not exist) [M7 age 改成 text] INSERT 成功;SELECT 成功;UPDATE 失败(operator does not exist: text + integer) [M8 age 从 int 改成 bigint] INSERT 成功;SELECT 成功;UPDATE 成功
up:status(文本)→ status_code(1 = active,2 = inactive,其它 = 0)并删除 status 列 down 之后表结构与迁移前相同(列 id/name/age/status),但数据校验和 不一致(前 135f5381… / 后 3ee62a91…);status 现在是:1 active ,2 inactive ,3 unknown ,4 unknown ,5 active(原来 c = pending、d = banned) v2 上线后写入了 3 行 email;回滚(DROP COLUMN email)再重新升级(ADD COLUMN email)后有 email 的行:0
[没有 serialVersionUID(v2 新增字段 coupon)]
v2 写文件;v1 读:Exception in thread "main" java.io.InvalidClassException: Order; local class incompatible: stream classdesc serialVersionUID = -7164839286763380304, local class serialVersionUID = 6748852704420637489
v1 写文件;v2 读:Exception in thread "main" java.io.InvalidClassException: Order; local class incompatible: stream classdesc serialVersionUID = 6748852704420637489, local class serialVersionUID = -7164839286763380304
[固定 serialVersionUID = 1(v2 新增字段 coupon)]
v2 写文件;v1 读:读到 Order{id=7, name=widget}
v1 写文件;v2 读:读到 Order{id=7, name=widget, coupon=null}
往返:v2 写入 coupon = VIP-99 → v2 自己读:读到 Order{id=7, name=widget, coupon=VIP-99}
回滚到 v1,v1 读出再写回(不改任何内容)→ 再升级回 v2 读:读到 Order{id=7, name=widget, coupon=null}(v1 的类里没有 coupon,字段被静默丢弃)[v2 只写新列 full_name] 共 200 行:v1 读 name 时看到 NULL 的行 100;v2 读 full_name 时看到 NULL 的行 100;回填 UPDATE ... SET full_name = name 之后 v2 看到 NULL 的行 0 [v2 双写 name 与 full_name] 共 200 行:v1 读 name 时看到 NULL 的行 0;v2 读 full_name 时看到 NULL 的行 100;回填 UPDATE ... SET full_name = name 之后 v2 看到 NULL 的行 0