2. Flyway 工作原理:基线、迁移、校验、修复、撤销的核心流程与状态机
Flyway 作为一款数据库版本控制工具,其核心工作流围绕五个关键动作展开:基线(Baseline)、迁移(Migrate)、校验(Validate)、修复(Repair) 和 撤销(Undo)。理解这些动作的触发条件、执行顺序以及它们如何改变数据库的状态,是掌握 Flyway 架构设计的基石。
2.1 核心动作与生命周期
Flyway 在目标数据库中维护一张元数据表(默认名为 flyway_schema_history),用于记录每一次迁移操作的执行记录。这张表是 Flyway 状态机的“持久化存储器”。下表总结了五个核心动作的职责与触发场景:
| 动作 | 命令 | 核心职责 | 典型触发场景 |
|---|---|---|---|
| 基线 | baseline |
将已有数据库标记为某个版本,作为后续迁移的起点 | 首次对非空数据库启用 Flyway |
| 迁移 | migrate |
按版本顺序应用待执行的迁移脚本 | 日常开发部署、CI/CD 流水线 |
| 校验 | validate |
对比已应用脚本的校验和与本地脚本是否一致 | 迁移前自动执行、手动检查 |
| 修复 | repair |
清理元数据表中的错误记录,重新计算校验和 | 迁移失败后、手动修改脚本后 |
| 撤销 | undo |
回退最近一次已应用的版本化迁移 | 紧急回滚、版本发布回退 |
关键认知: 迁移(Migrate)是 Flyway 最核心的动作,其他四个动作均围绕如何安全、可控地执行迁移而设计。撤销(Undo)并非迁移的逆操作,而是通过执行独立的撤销脚本来实现逻辑回滚。
2.2 状态机与元数据表状态流转
Flyway 的状态机模型以 flyway_schema_history 表中的 success 字段为核心。每次执行迁移或撤销后,该字段会被标记为 1(成功)或 0(失败)。下图展示了从基线到迁移、再到校验与修复的完整状态流转路径:
2.3 基线(Baseline)—— 从已有数据库开始
当 Flyway 首次连接到一个非空数据库时,直接执行 migrate 会失败,因为元数据表不存在且数据库已有对象。基线操作解决了这个“冷启动”问题:
- 执行效果: 创建
flyway_schema_history表,并插入一条版本号为基线版本(如 1)的记录,标记为success=1。 - 后续影响: 所有版本号小于或等于基线版本的迁移脚本将被 Flyway 忽略,仅执行版本号大于基线版本的脚本。
- 最佳实践: 基线版本号应略高于当前数据库的实际版本,避免与已有脚本冲突。
实战案例: 某项目数据库已有 20 张表,团队决定引入 Flyway。执行
flyway baseline -baselineVersion=2.0,Flyway 将当前数据库状态标记为版本 2.0,后续只需编写版本 2.1、2.2 等增量脚本。
2.4 迁移(Migrate)—— 版本递增的核心引擎
迁移是 Flyway 最频繁执行的动作。其执行流程遵循严格的版本顺序和事务安全原则:
- 扫描脚本: 读取配置的迁移脚本路径(如
filesystem:sql/migrations),按版本号排序。 - 比对元数据: 查询
flyway_schema_history表,获取已应用的版本列表。 - 计算待执行列表: 找出本地有但元数据表中没有的版本,按顺序排列。
- 逐版本执行: 对每个待执行脚本,在一个数据库事务中执行 SQL,执行成功后插入一条记录(
success=1)。 - 失败处理: 如果某个脚本执行失败,Flyway 会回滚该事务,并在元数据表中插入一条
success=0的记录,阻止后续迁移继续执行。
注意: Flyway 默认将每个迁移脚本包装在独立事务中执行。如果数据库不支持 DDL 事务(如 MySQL 的某些 DDL 语句),则无法回滚已执行的 DDL 变更。此时建议将 DDL 和 DML 分离到不同脚本中。
2.5 校验(Validate)—— 防止脚本篡改
校验动作的核心目的是检测已应用脚本是否被意外修改。Flyway 在每次迁移前会自动执行校验,也可以手动触发:
- 校验机制: Flyway 计算每个本地迁移脚本的 CRC32 校验和,并与
flyway_schema_history表中存储的校验和进行比对。 - 校验失败场景:
- 本地脚本内容被修改,校验和不匹配。
- 本地脚本被删除,但元数据表中仍有记录。
- 本地脚本版本号与元数据表中的版本号冲突(如版本号重复但内容不同)。
- 失败后果: 校验失败后,
migrate命令会立即中止,防止不一致的数据库状态扩散。
2.6 修复(Repair)—— 清理元数据异常
当校验失败或迁移过程中发生异常导致元数据表出现“脏记录”时,repair 命令用于恢复一致性:
| 修复场景 | 修复行为 |
|---|---|
存在 success=0 的记录 |
删除该失败记录,允许重新执行对应版本的迁移 |
| 本地脚本校验和与记录不一致 | 用本地脚本的新校验和更新元数据表(需人工确认修改是合法的) |
| 已应用的脚本被删除 | 从元数据表中删除对应记录(谨慎使用,可能导致数据丢失) |
| 基线版本被误标记 | 删除基线记录,允许重新执行基线操作 |
安全建议: 修复操作应视为“紧急手术”,在生产环境中执行前务必备份
flyway_schema_history 表。优先通过版本控制回滚代码,而非直接修复元数据。
2.7 撤销(Undo)—— 有条件的回退
撤销动作并非 Flyway 的默认行为,它需要满足以下条件才能生效:
- 撤销脚本存在: 必须为每个版本化迁移编写对应的撤销脚本(命名格式:
U<version>__<description>.sql)。 - 仅回退最近版本: Flyway 每次只撤销一个版本,即当前
flyway_schema_history表中版本号最高的已成功应用记录。 - 执行流程:
- 查找最新版本的撤销脚本。
- 在事务中执行撤销脚本。
- 从元数据表中删除该版本的迁移记录(而非标记为失败)。
- 限制: 撤销脚本必须手动维护,且无法回退基线操作。对于重复迁移(Repeatable Migration),撤销不适用。
-- 示例:撤销脚本命名
-- 迁移脚本:V2__add_user_table.sql
-- 撤销脚本:U2__add_user_table.sql
-- U2__add_user_table.sql 内容示例
DROP TABLE IF EXISTS users;
2.8 状态机总结:从应用到回退的完整闭环
Flyway 的状态机设计确保了数据库版本管理的可追溯性和可恢复性。以下表格总结了不同状态下各动作的可用性:
| 当前状态 | 可用动作 | 不可用动作 | 说明 |
|---|---|---|---|
| 无元数据表 | baseline, migrate | validate, repair, undo | 首次使用,需先创建元数据表 |
| 已基线化 | migrate, validate | baseline, undo | 基线只能执行一次 |
| 迁移成功 | migrate, validate, undo | baseline | 可继续升级或回退 |
| 迁移失败 | repair, validate | migrate, undo | 必须先修复脏记录 |
| 校验失败 | repair | migrate, undo | 修复后重新校验 |
| 撤销成功 | migrate, validate | baseline, undo | 回退后可重新迁移 |