2、Flyway工作原理:元数据表、版本号解析与锁定机制

Flyway 的核心设计围绕“版本化数据库迁移”展开。每一次对数据库结构的变更(DDL/DML)都被封装为一个迁移脚本,并由 Flyway 按照版本号顺序依次执行。为了确保迁移过程的可追溯、幂等与并发安全,Flyway 依赖一套严谨的元数据管理、版本号解析与锁定机制。本章将深入剖析这三个核心环节。

2.1 Flyway 元数据表:flyway_schema_history

Flyway 在首次执行迁移时,会自动在目标 Schema 中创建一张名为 flyway_schema_history 的元数据表。这张表是 Flyway 的“大脑”,记录了所有已执行迁移的完整历史。每次迁移执行后,Flyway 都会向该表插入一条记录;每次启动时,Flyway 都会读取该表以确定当前数据库状态。

2.1.1 表结构定义

下表列出了 flyway_schema_history 的核心字段及其语义:

字段名 数据类型 说明
installed_rank INTEGER 执行顺序序号,从 1 开始递增,用于确定迁移的执行顺序
version VARCHAR(50) 迁移版本号,如 1、2.1、20250101.1;可重复迁移(Repeatable)该字段为 NULL
description VARCHAR(200) 迁移脚本的描述信息,取自文件名中的描述部分
type VARCHAR(20) 迁移类型:SQL、JDBC、SPRING_JDBC 等
script VARCHAR(1000) 迁移脚本的完整文件名,如 V2__add_users.sql
checksum INTEGER 脚本内容的校验和(CRC32),用于检测脚本是否被篡改
installed_by VARCHAR(100) 执行迁移的数据库用户
installed_on TIMESTAMP 迁移执行完成的时间戳
execution_time INTEGER 迁移执行耗时,单位为毫秒
success BOOLEAN 执行是否成功:1 表示成功,0 表示失败
实践要点:在生产环境中,flyway_schema_history 表应被纳入日常备份策略。一旦该表损坏或丢失,Flyway 将无法感知当前数据库的版本状态,可能导致重复迁移或数据丢失。

2.2 迁移版本号解析机制

Flyway 通过文件名解析迁移版本号,并依据版本号决定执行顺序。版本号解析遵循一套明确的规则,理解这些规则是避免迁移冲突的前提。

2.2.1 文件命名规范

Flyway 迁移脚本的文件名由三部分组成:

前缀 + 版本号 + 分隔符 + 描述 + 后缀

示例:
V1__init_schema.sql
V2.1__add_user_table.sql
V2025_01_01__create_index.sql
R__daily_cleanup.sql   (Repeatable 迁移,无版本号)
  • 前缀:V 表示版本化迁移(Versioned),R 表示可重复迁移(Repeatable)。
  • 版本号:由数字和点(.)或下划线(_)组成,如 1、2.1、2025_01_01。
  • 分隔符:双下划线 __,用于分隔版本号和描述。
  • 描述:简短说明,单词间可用下划线或空格。
  • 后缀:.sql 或 .java 等。

2.2.2 版本号排序规则

Flyway 将版本号视为由数字和点分隔的多段数值,按从左到右逐段比较的方式排序:

版本号 解析后的数值数组 排序位置
1 [1] 1
1.1 [1, 1] 2
1.2 [1, 2] 3
2 [2] 4
2.0.1 [2, 0, 1] 5
10 [10] 6
常见陷阱:版本号 10 会被解析为数值 10,排在 9 之后。不要使用前导零(如 01),因为 Flyway 会将其解析为 1,导致排序混乱。推荐使用语义化版本号,如 1.0.0、2.1.3。

2.3 校验与锁定机制

Flyway 通过校验(Checksum)和锁定(Lock)两大机制,确保迁移的完整性与并发安全性。

2.3.1 校验机制:防止脚本篡改

每次迁移执行后,Flyway 会计算脚本内容的 CRC32 校验和,并存入 flyway_schema_history.checksum 字段。下次启动时,Flyway 会重新计算已执行脚本的校验和,并与数据库记录比对:

  • 校验通过:脚本未被修改,继续执行后续迁移。
  • 校验失败:抛出 FlywayException,阻止应用启动,防止因脚本篡改导致的数据不一致。
校验触发条件:仅对 已执行过 的版本化迁移脚本进行校验。可重复迁移(R__)每次执行前都会重新计算校验和,若发现变化则重新执行。

2.3.2 锁定机制:防止并发冲突

在微服务或多实例部署场景下,多个应用实例可能同时启动并尝试执行迁移。Flyway 使用数据库级别的表锁来保证同一时刻只有一个实例执行迁移操作。

锁定流程如下:

  1. 实例 A 尝试获取 flyway_schema_history 表的写锁。
  2. 若获取成功,实例 A 开始执行迁移;其他实例(B、C)等待锁释放。
  3. 实例 A 迁移完成后释放锁。
  4. 实例 B 获取锁后,发现数据库版本已是最新,跳过迁移直接启动应用。

下图展示了 Flyway 从启动到完成迁移的完整工作流程,包含校验与锁定环节:

Flyway 启动与迁移执行流程 应用启动 读取 flyway_schema_history 获取表锁 校验已执行脚本 Checksum 执行待迁移脚本 更新元数据表 释放锁 / 应用启动完成 校验失败时: 抛出 FlywayException 阻止应用启动 需人工介入修复 锁等待超时: 默认等待 60 秒 超时后抛出异常

2.4 关键机制对比总结

下表从目的、触发时机、失败影响三个维度对比校验与锁定机制:

机制 目的 触发时机 失败影响
校验(Checksum) 检测已执行脚本是否被篡改 每次应用启动时 应用启动失败,需人工修复脚本或手动更新 checksum
锁定(Lock) 防止多实例并发执行迁移 迁移执行前获取锁,执行后释放 锁等待超时后抛出异常,需检查其他实例状态
性能调优提示:在异步任务或高并发场景下,可通过配置 flyway.lock-retry-count 和 flyway.lock-retry-interval 调整锁重试策略,避免因短暂锁冲突导致任务失败。默认重试次数为 1 次,间隔为 1 秒。