Flyway 工作原理及最佳实践

3,514 阅读7分钟

背景

Flyway 是一款开源的数据库版本管理工具,旨在简化数据库变更的管理与跟踪。它支持数据库的自动升级,并且配置简单,帮助团队更有效地管理数据库的变更。Flyway 可以在 resources/db/migration 目录下创建 SQL 文件,项目启动时会自动执行这些文件中的 SQL 语句,完成数据库的变更。

工作原理

  1. 历史记录表:在首次执行时,Flyway 会创建一个名为 flyway_schema_history 的历史记录表,用于跟踪数据库的状态。每次项目启动时,Flyway 会扫描 resources/db/migration 目录下的文件,检查版本号,并通过查询 flyway_schema_history 表来判断是否需要进行迁移。

  2. 默认路径:Flyway 默认从 classpath:db/migration 路径查找迁移文件,对应的 SQL 文件放置在 src/main/resources/db/migration 下,Java 类放置在 src/main/java/db/migration 下。

  3. 版本校验:为了确保数据库变更的可靠性和一致性,Flyway 采用严格的版本校验机制。每次执行数据库升级时,Flyway 会计算已执行脚本的 Checksum(校验和),并与 flyway_schema_history 表中存储的 Checksum 进行比对。如果脚本内容发生变化而版本号未变,Flyway 会检测到 Checksum 的差异,并抛出异常,防止不一致的脚本被执行。Checksum 的计算基于 CRC32(循环冗余校验码)。

  4. 版本号比较:对于新增的脚本,Flyway 会根据版本号进行比较。如果新增脚本的版本号小于当前数据库中已执行的最后一个版本号,Flyway 将不会执行该脚本,以避免版本倒退或重复执行。

  5. 启动速度:Flyway 的启动速度主要取决于两个因素:

    • IO 开销:依次读取脚本内容时的 I/O 操作。
    • Checksum 计算:每次执行时计算脚本的 Checksum 值。

配置详解

flyway:
  enabled: true  # 是否开启 Flyway,默认为 true
  baseline-on-migrate: true  # 迁移时发现目标 schema 非空且没有元数据表时,是否自动执行基准迁移
  locations: classpath:db/migration  # 迁移脚本的位置,默认为 db/migration
  encoding: UTF-8  # 设置迁移时的编码,默认为 UTF-8
  clean-disabled: true  # 是否允许在执行 SQL 前清除已有的表,生产环境建议设置为 true,默认为 false
  out-of-order: true  # 是否允许无序的迁移,默认为 false
  placeholder-prefix: $$  # Placeholder 的前缀,默认为 ${
  baseline-version: 1  # 基准迁移时对现有 schema 的版本打标签,默认为 1

依赖管理

确保 Flyway 的版本与 Spring Boot 版本相匹配。例如:

<dependency>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-core</artifactId>
  <version>6.5.7</version>  # 请根据 Spring Boot 版本选择合适的 Flyway 版本
</dependency>

创建 SQL 文件

Flyway 的 SQL 文件命名规则如下:

  1. 仅需要执行一次的脚本:以大写 V 开头,V+版本号__文件描述+后缀名,例如 V1.0__create_users_table.sql
  2. 需要执行多次的脚本:以大写 R 开头,例如 R__clean.sqlR 开头的脚本不带版本号,每次修改后都会重新执行。
  3. 优先级V 开头的脚本优先级高于 R 开头的脚本。

执行方式及注意事项

  1. 避免修改已执行的 SQL 文件:Flyway 的设计原则是不可变性,即一旦某个 SQL 脚本成功执行并记录到 flyway_schema_history 表中,就不应该再修改该文件。原因如下:
  • Checksum 校验:Flyway 会计算每个迁移脚本的 Checksum,并与 flyway_schema_history 表中存储的 Checksum 进行比对。如果脚本的内容发生了修改,而版本号没有变化,Flyway 会检测到 Checksum 的差异,并抛出异常,阻止不一致的脚本被重新执行。
  • 避免数据不一致:修改已经执行过的脚本可能导致数据库状态与应用程序的预期不一致,进而引发各种问题。

建议:需要进行新的变更,建议新建一个 SQL 文件,并使用新的版本号(例如 V2.0.1__new_change.sql)。这样可以确保 Flyway 正确识别并执行新的变更,同时不会影响已执行的迁移。

  1. 修改文件名:需要更改文件名,可以在 flyway_schema_history 表中手动修改相应的记录。但请注意,这种操作应非常谨慎,尤其是在生产环境中。

常见问题及解决方案

1. 修改已执行的 SQL 文件导致报错

如前所述,Flyway 不允许修改已执行的 SQL 文件。如果不小心修改了这些文件,会导致 Flyway 在重新启动时抛出异常。解决方法有两种:

  • 新建 SQL 文件:不要修改已执行的文件,而是创建一个新的 SQL 文件,包含 需要的变更。
  • 删除历史记录并重试(谨慎使用):如果 确实需要重新执行已修改的脚本,可以在 flyway_schema_history 表中找到相关记录并删除。然后,重新启动应用,Flyway 会重新执行这些脚本。请注意,这样做可能会导致数据不一致,因此在生产环境中应极其谨慎。

2. 首次运行时手动停止,导致后续执行异常

如果 在首次运行时手动停止了应用,可能会导致部分迁移脚本没有完全执行,甚至在 flyway_schema_history 表中留下了不完整的记录。后续重新运行时,Flyway 可能会遇到字段不存在等异常情况。

解决方案

  • 清理未完成的迁移:首先,检查 flyway_schema_history 表,确保所有已执行的迁移脚本都有成功的记录。如果有失败的记录,可以尝试删除这些失败的记录,或者使用 Flyway 的 repair 命令来修复历史表。
  • 删除残留的数据:如果 在手动停止应用后清除了所有相关表的数据,确保同时删除了依赖于这些表的存储过程、函数和其他数据库对象。在 的问题中,提到“存储过程还有函数没有删除干净”,这可能是导致后续执行失败的原因。确保所有相关的数据库对象都被正确删除或更新,以避免字段不存在等错误。
  • 使用 Flyway.repair():Flyway 提供了 repair 命令,可以修复 flyway_schema_history 表中不一致的记录。 可以运行以下命令来修复历史表:
flyway -url=jdbc:your-database-url -user=your-username -password=your-password repair

这个命令会清理掉失败的迁移记录,并确保历史表中的数据与实际数据库状态一致。

可能遇到的问题:mysql数据库中忘记删除存储过程中的脚本,导致启动失败。

总结

Flyway 是一个强大的数据库版本管理工具,但使用时需要注意以下几点:

  • 不要修改已执行的 SQL 文件,而是新建文件来包含新的变更。
  • 谨慎处理部分执行的迁移,确保数据库结构和 flyway_schema_history 表中的记录一致。
  • 清理残留的数据库对象,避免因依赖对象未删除而导致的错误。
  • 使用 Flyway.repair() 命令,修复历史表中的不一致记录。

通过遵循这些最佳实践, 可以避免常见的执行问题,并确保 Flyway 在 的项目中高效、稳定地工作。

附录:Flyway 配置项说明

  • flyway.baseline-description:对执行迁移时基准版本的描述,默认为 << Flyway Baseline >>
  • flyway.baseline-on-migrate:当迁移时发现目标 schema 非空且没有元数据表时,是否自动执行基准迁移,默认为 false
  • flyway.baseline-version:开始执行基准迁移时对现有 schema 的版本打标签,默认为 1
  • flyway.check-location:检查迁移脚本的位置是否存在,默认为 false
  • flyway.clean-on-validation-error:当发现校验错误时是否自动调用 clean,默认为 false
  • flyway.enabled:是否开启 Flyway,默认为 true
  • flyway.encoding:设置迁移时的编码,默认为 UTF-8
  • flyway.ignore-failed-future-migration:当读取元数据表时是否忽略错误的迁移,默认为 false
  • flyway.init-sqls:当初始化好连接时要执行的 SQL,默认为空。
  • flyway.locations:迁移脚本的位置,默认为 db/migration
  • flyway.out-of-order:是否允许无序的迁移,默认为 false
  • flyway.password:目标数据库的密码。
  • flyway.placeholder-prefix:设置每个 placeholder 的前缀,默认为 ${
  • flyway.placeholder-replacement:是否替换 placeholder,默认为 true
  • flyway.placeholder-suffix:设置每个 placeholder 的后缀,默认为 }
  • flyway.placeholders.[placeholder name] :设置 placeholder 的值。
  • flyway.schemas:设定需要迁移的 schema,大小写敏感,默认为连接默认的 schema。
  • flyway.sql-migration-prefix:迁移文件的前缀,默认为 V
  • flyway.sql-migration-separator:迁移脚本的文件名分隔符,默认为 __
  • flyway.sql-migration-suffix:迁移脚本的后缀,默认为 .sql
  • flyway.table:Flyway 使用的元数据表名,默认为 flyway_schema_history
  • flyway.target:迁移时使用的目标版本,默认为最新的版本。
  • flyway.url:迁移时使用的 JDBC URL,如果没有指定,将使用配置的主数据源。
  • flyway.user:迁移数据库的用户名。
  • flyway.validate-on-migrate:迁移时是否校验,默认为 true