2. Flyway 基础回顾:工作原理、迁移脚本执行流程与版本控制机制
在深入联合调优之前,必须牢固掌握 Flyway 的核心运行机制。本章将拆解 Flyway 的三大支柱:工作原理、迁移脚本执行流程以及版本控制机制。这些基础是后续分析“执行慢”与“GC 停顿”之间关联的起点。
2.1 Flyway 工作原理
Flyway 是一个基于 约定优于配置 的数据库版本控制工具。它通过一个专用的元数据表(默认名为 flyway_schema_history)来跟踪数据库的当前状态,并自动应用尚未执行过的迁移脚本。
核心原则: Flyway 将数据库视为一个状态机,每次迁移都是从一个状态到下一个状态的确定性转换。它不关心数据库的当前内容,只关心
flyway_schema_history 表中的记录。
其工作流程可概括为三个步骤:
- 扫描: Flyway 扫描文件系统中指定路径(如
classpath:db/migration)下的所有迁移脚本。 - 校验: 将扫描到的脚本与
flyway_schema_history表中的已执行记录进行比对,检查版本号、校验和(Checksum)是否一致,防止脚本被篡改。 - 迁移: 按版本号顺序执行所有尚未应用的脚本,并将执行记录写入元数据表。
性能提示: 扫描和校验阶段通常很快(毫秒级),但迁移阶段的 SQL 执行时间以及 Flyway 自身的事务提交、元数据表写入操作,是 GC 停顿的主要关注点。
2.2 迁移脚本执行流程
Flyway 的迁移执行流程是一个严格的事务性过程。理解每个步骤的资源消耗,是定位性能瓶颈的关键。
| 步骤 | 操作 | 资源消耗特征 | GC 关联性 |
|---|---|---|---|
| 1. 获取锁 | 尝试获取数据库集群级别的排他锁(基于元数据表)。 | 网络 I/O,短等待 | 低 |
| 2. 开始事务 | 开启数据库事务(BEGIN)。 |
数据库内部操作 | 低 |
| 3. 执行 SQL | 逐条执行迁移脚本中的 DDL/DML 语句。 | CPU 密集型(解析、优化)、I/O 密集型(数据页读写) | 高(大量对象创建、大结果集处理) |
| 4. 更新元数据 | 向 flyway_schema_history 表插入一条新记录。 |
单行写入,极快 | 低 |
| 5. 提交事务 | 提交事务(COMMIT),释放锁。 |
日志刷盘(I/O) | 中(事务日志缓冲区刷新) |
调优关注点: 步骤 3(执行 SQL)是耗时和内存消耗的绝对主体。如果迁移脚本包含大量数据操作(如
INSERT ... SELECT、UPDATE 大表),会导致 JVM 堆内存迅速膨胀,频繁触发 Young GC 甚至 Full GC。
2.3 版本控制机制
Flyway 通过 版本号 和 校验和 实现精确的版本控制。版本号决定了脚本的执行顺序,校验和保证了脚本的不可篡改性。
2.3.1 版本号命名规范
迁移脚本的文件名遵循严格的模式,Flyway 据此解析版本号和描述信息。
模式:{Prefix}{Version}__{Description}.sql
示例:V1.0.1__create_user_table.sql
V2.1__add_email_index.sql
V20231027__migrate_legacy_data.sql
- Prefix: 默认为
V(版本迁移),也可用U(撤销迁移,需付费版)或R(可重复迁移)。 - Version: 支持点分数字(如
1.0.1)、时间戳(如20231027)等。版本号必须唯一且递增。 - Separator: 双下划线
__用于分隔版本号和描述。 - Description: 描述性文字,最终会写入元数据表。
2.3.2 校验和(Checksum)机制
Flyway 在第一次执行脚本时,会计算其内容的 CRC32 校验和 并存储在 flyway_schema_history 表中。后续运行时,Flyway 会重新计算脚本的校验和并与数据库记录比对。
校验失败场景: 如果某个已执行的脚本被修改(哪怕只改了一个空格),Flyway 在校验阶段会抛出异常并阻止迁移继续。这是保证数据库版本与代码版本严格一致的关键防线。
2.4 关键概念总结
| 概念 | 说明 | 对性能调优的意义 |
|---|---|---|
| 元数据表 | flyway_schema_history,记录每次迁移的元信息。 |
频繁的读写操作(尤其是高并发场景)可能成为锁竞争点。 |
| 版本号 | 决定脚本执行顺序,支持语义化版本。 | 版本号冲突会导致迁移失败,需在 CI/CD 流程中严格管理。 |
| 校验和 | CRC32 算法,保证脚本内容未被篡改。 | 校验过程本身开销极低,但失败后的排查成本较高。 |
| 事务性迁移 | 默认每个迁移脚本在一个事务中执行。 | 大事务会持有锁和数据库连接资源,增加 GC 压力(事务日志对象)。 |