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(失败)。下图展示了从基线到迁移、再到校验与修复的完整状态流转路径:

Flyway 核心状态机与动作流转图 Flyway 核心状态机与动作流转图 初始状态(无元数据表) 基线(Baseline) 已基线化(Baseline Applied) 迁移(Migrate) 校验(Validate) 修复(Repair) 撤销(Undo) 最新版本(Current) baseline migrate validate 成功 通过 失败 修复完成 undo 回退后重新迁移 说明:实线箭头表示正向流程,虚线箭头表示异常或回退路径。所有状态变更均记录在 flyway_schema_history 表中。

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 最频繁执行的动作。其执行流程遵循严格的版本顺序和事务安全原则:

  1. 扫描脚本: 读取配置的迁移脚本路径(如 filesystem:sql/migrations),按版本号排序。
  2. 比对元数据: 查询 flyway_schema_history 表,获取已应用的版本列表。
  3. 计算待执行列表: 找出本地有但元数据表中没有的版本,按顺序排列。
  4. 逐版本执行: 对每个待执行脚本,在一个数据库事务中执行 SQL,执行成功后插入一条记录(success=1)。
  5. 失败处理: 如果某个脚本执行失败,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 表中版本号最高的已成功应用记录。
  • 执行流程:
    1. 查找最新版本的撤销脚本。
    2. 在事务中执行撤销脚本。
    3. 从元数据表中删除该版本的迁移记录(而非标记为失败)。
  • 限制: 撤销脚本必须手动维护,且无法回退基线操作。对于重复迁移(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 回退后可重新迁移