ERP对接程序升级时提示"数据库迁移第N版失败",数据库就一定退回升级前的完整状态吗?要看迁移事务包住的是每个版本,还是整次升级。
本文依据项目真实SQLite初始化函数,用新建实验库和进程内临时追加的故障SQL,验证逐版本事务的回滚范围。没有编辑项目迁移源码,没有操作生产库或启动服务。
1. 当前迁移以版本为事务边界
代码读取 schema_version.v,从该位置继续执行 MIGRATIONS 数组。每个版本单独开启事务,核心顺序是:
javascript
db.exec('BEGIN');
try {
db.exec(MIGRATIONS[i]);
db.prepare('UPDATE schema_version SET v = ?').run(i + 1);
db.exec('COMMIT');
} catch (e) {
db.exec('ROLLBACK');
throw new Error(`数据库迁移第 ${i + 1} 版失败:${e.message}`);
}
一个版本内,结构变化与版本号更新在同一事务中提交。但前一个版本的COMMIT已经完成,不会因为下一个版本失败而自动撤销。
因此,"本次迁移失败"要进一步拆成:哪个版本失败,该版本有没有提交,以及此前哪些版本已经提交。
2. 用故障版本验证实际结果
当前项目真实迁移数组共有3版。本地验证仅在当前Node进程中临时追加一个第4版,不把它写进项目源码:
sql
CREATE TABLE demo_migration_probe (id INTEGER);
INSERT INTO demo_missing_table(id) VALUES (1);
第一条创建演示表,第二条因演示目标表不存在而失败。新建实验库从头执行初始化,得到:
| 检查项 | 本地结果 |
|---|---|
| 抛出的失败版本 | 第4版 |
| 失败后schema_version.v | 3 |
| 第3版创建的source_runs表 | 存在 |
| 第4版刚创建的演示表 | 不存在 |
第4版内已经执行的建表被回滚,版本号没有推进到4;此前已提交的前三版仍然保留。
这里的第4版只是人为构造,不是项目已经发布过的数据库升级,也不是客户升级事故。检查使用独立数据库连接读取实验文件,验证的是SQLite中的持久结果。

图1:当前迁移函数加构造故障SQL的本地结果,未修改生产数据库。
3. 失败以后,先读版本,不要先改版本
排查时建议先只读核对当前版本与实际表结构:
sql
SELECT v FROM schema_version;
SELECT name FROM sqlite_master WHERE type = 'table';
同时确认打开的是哪份数据库、使用的是哪个应用版本,以及报错指向的迁移SQL。数据目录配置或部署路径不一致,也可能让人检查到另一份文件。
不要看到失败就把v改成0。此前的表可能已经存在,重跑历史建表语句可能再次失败;更重要的是,手工改数字会破坏版本与结构之间的对应关系。
也不要直接把v填成更高版本来跳过报错。数字推进了,不代表缺失的表、字段和数据变换已经完成。
4. 修复失败版本需要考虑哪些条件
先确认故障版本是否完全处于这份数据库事务的控制范围。文件操作、外部接口或其他数据库连接的变化,不会因为这里执行ROLLBACK就一并撤销。
对尚未提交的失败迁移,要核对它的输入结构和执行前提;对已经在其他环境成功执行过的历史版本,则不能随意改写SQL,应该设计兼容升级与验证方案。
当前循环是按顺序继续,失败版本没有通过,后续版本也不会执行。因此仅在末尾增加一个版本,不能自然绕过前面仍会报错的步骤。
是否将整批升级放入一个事务、是否允许分阶段提交,应结合数据量、锁定时间与数据库能力决定。本文说明当前实现的行为,没有给出适用于所有项目的迁移框架。
5. 升级验收不能只看启动成功
建议先在隔离副本上验证旧版本到新版本的结构和数据,再故意制造某个版本失败,核对版本号、部分SQL的回滚、错误记录与再次执行条件。
还应验证已有数据是否保留、业务查询是否能正常读取,以及应用版本是否认识当前数据库版本。需要降级时,不能假定旧应用会自动撤销新结构。
本次已经验证逐版本事务的失败边界:第4版回滚,前三版保留。没有实测生产大表升级、锁竞争、断电恢复或跨版本降级。数据库升级报告应把这些范围分别记录,不能用一次构造回滚通过替代全部上线验收。