4、Flyway实战:项目初始化、迁移脚本命名规范、版本回滚策略

好,咱们开始动手。前面聊了那么多理论,这一章我们直接上 Flyway。我个人的习惯是,学一个工具最好的方式就是把它跑起来,然后故意搞坏它,再看怎么修。所以这一章,我会带着你从零初始化一个项目,然后一步步踩坑、填坑。

4.1 项目初始化:从零搭一个 Flyway 环境

先说说环境。我假设你本地已经装了 Java 8+ 和 Maven 或 Gradle。Flyway 支持命令行、API 嵌入、Maven 插件、Gradle 插件等多种方式。我个人最常用的是 Maven 插件,因为和 CI/CD 流水线集成最方便。

创建一个空的 Maven 项目,在 pom.xml 里加上 Flyway 插件:

<plugin>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-maven-plugin</artifactId>
    <version>9.22.3</version>
    <configuration>
        <url>jdbc:mysql://localhost:3306/schema_demo</url>
        <user>root</user>
        <password>your_password</password>
    </configuration>
</plugin>

然后在 src/main/resources/db/migration 目录下放你的迁移脚本。Flyway 默认会扫描这个路径。嗯,这里要注意:路径不能改错,否则 Flyway 会找不到脚本,直接报错。

我第一次用 Flyway 时就犯过这个错——把脚本放到了 db/migrations,结果跑了半天都是空的。后来查文档才发现,默认路径是 db/migration,少了个 s。

小提示: 如果你用的是 Spring Boot,可以在 application.yml 里通过 spring.flyway.locations 自定义路径。但建议保持默认,减少团队沟通成本。

4.2 迁移脚本命名规范:版本号、描述、校验和

命名规范是 Flyway 的核心。说白了,Flyway 就是靠文件名来排序和校验的。命名格式如下:

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

示例:
V1__create_user_table.sql
V2__add_email_column.sql
V2_1__add_index_on_email.sql

拆开来看:

  • 前缀:V 表示版本化迁移,U 表示撤销迁移,R 表示可重复迁移。
  • 版本号:数字或点号分隔的数字,比如 1、2.1、20240101。Flyway 会按版本号升序执行。
  • 分隔符:两个下划线 __,不能少。
  • 描述:用下划线代替空格,比如 create_user_table。
  • 后缀:.sql。

为什么版本号要用点号?我在项目中遇到过一种情况:团队有 10 个人同时开发,每个人都在本地写迁移脚本。如果只用整数版本号,合并时很容易冲突。用 V1.1、V1.2 这种子版本号,每个人在自己的分支上用不同的子版本,合并时基本不会撞车。

命名示例 说明
V1__create_user_table.sql 第一个版本,建用户表
V2__add_email_column.sql 第二个版本,加 email 字段
V2_1__add_index_on_email.sql 2.1 版本,加索引
U2__add_email_column.sql 撤销 V2 的迁移
R__seed_data.sql 可重复迁移,每次运行都会重新执行
警告: 一旦脚本被执行过,不要修改已执行脚本的内容。Flyway 会计算每个脚本的校验和(checksum),如果内容变了,下次运行会报校验和不匹配的错误。我曾经因为改了一个已上线的脚本,导致整个 CI 流水线挂了半小时。

4.3 版本回滚策略:U 脚本、手动回滚、基线重置

回滚是 Schema 演进里最头疼的事。Flyway 官方其实不太推荐自动回滚,因为 SQL 的回滚很难做到无损。但现实项目中,我们总得有个兜底方案。

Flyway 提供了三种回滚方式:

4.3.1 使用 U 脚本(Undo Migration)

U 脚本和 V 脚本一一对应。比如你有一个 V2__add_email_column.sql,对应的撤销脚本就是 U2__add_email_column.sql。内容就是反向操作:

-- V2__add_email_column.sql
ALTER TABLE users ADD COLUMN email VARCHAR(255);

-- U2__add_email_column.sql
ALTER TABLE users DROP COLUMN email;

执行回滚命令:

mvn flyway:undo

但说实话,我在生产环境几乎不用 U 脚本。为什么?因为回滚操作本身也是 DDL,如果回滚过程中数据库连接断了,或者有其他事务锁住了表,你会陷入一个更尴尬的境地。而且,U 脚本在 Flyway 社区版里是收费功能,开源版不支持。

4.3.2 手动回滚:最稳妥的方式

我个人最推荐的方式是手动回滚。流程是这样的:

  1. 写一个反向 SQL 脚本,手动在数据库里执行。
  2. 执行 flyway repair 命令,清除 Flyway 的迁移记录。
  3. 删除或修改对应的 V 脚本,重新部署。

举个例子:假设你误执行了 V3__drop_old_column.sql,把 old_field 删了。手动回滚步骤:

-- 1. 手动恢复字段
ALTER TABLE users ADD COLUMN old_field VARCHAR(100);

-- 2. 清除 V3 的迁移记录
mvn flyway:repair

-- 3. 修改 V3 脚本内容,或者直接删掉 V3 文件

这样做的好处是,你完全掌控了回滚过程,不会因为自动回滚的原子性问题导致数据不一致。

核心原则: 回滚不是删除 Schema 变更,而是用新的变更来修正旧的变更。换句话说,能加字段就别删字段,能加表就别删表。保留历史,用新增来修正。

4.3.3 基线重置(Baseline)

还有一种场景:你接手了一个老项目,数据库里已经有几十张表了,但之前没用 Flyway。这时候你不能从零开始跑所有脚本,而是要用基线功能。

mvn flyway:baseline -Dflyway.baselineVersion=1

这条命令会在 flyway_schema_history 表里插入一条记录,标记当前数据库状态为版本 1。之后你写的脚本从版本 2 开始,Flyway 只会执行版本 2 及之后的脚本。

我记得有一次帮客户做数据库迁移,他们的生产库有 200 多张表,但没有任何版本管理。我用了基线功能,把当前状态标记为 V1,然后从 V2 开始管理后续变更。整个过程花了不到 10 分钟,客户都惊呆了。

4.4 核心流程:一张图看懂 Flyway 工作流

下面这张图展示了 Flyway 从初始化到回滚的完整流程。我把它画成了 SVG,方便你理解。

Flyway 工作流:初始化、迁移、回滚 Flyway 工作流:初始化 → 迁移 → 回滚 项目初始化 配置 pom.xml + 路径 编写 V 脚本 V1__create_table.sql 执行迁移 mvn flyway:migrate 校验成功 记录到 flyway_schema_history 需要回滚 手动 / U 脚本 / 基线 回滚执行 flyway:undo / repair 修复记录 flyway:repair 重新迁移 修改后再次 migrate 💡 说明: 回滚不是删除历史,而是用新的变更修正旧的变更。优先使用手动回滚 + repair,避免自动回滚带来的原子性问题。

4.5 避坑指南:我踩过的几个坑

最后,分享几个我在实战中踩过的坑,希望能帮你省点时间。

  • 坑一:脚本执行顺序依赖。有一次我写了 V2__add_index.sql 和 V2_1__add_column.sql,结果 V2_1 先执行了,因为 Flyway 按字符串排序,V2_1 比 V2 大。解决方案:版本号用 V2.0 和 V2.1,点号分隔的数字排序更符合直觉。
  • 坑二:多人并行开发时的版本冲突。两个同事同时写了 V3__xxx.sql,合并时冲突。我的做法是:每个开发者在自己的分支上用 V{分支号}_{序号} 的格式,比如 V20240101_1__xxx.sql,合并时再统一重命名。
  • 坑三:生产环境误执行了错误的脚本。别慌,先锁住数据库不让其他人写,然后手动执行反向 SQL,再用 flyway:repair 清除记录。千万不要直接删 flyway_schema_history 表里的记录,否则 Flyway 会认为那个版本没执行过,下次部署时会重新执行,导致重复操作。
再次强调: 永远不要手动修改 flyway_schema_history 表。那是 Flyway 的命根子。我见过有人直接 delete 记录,结果导致生产环境数据丢失,最后花了整整一个周末才恢复。

好了,这一章的内容就到这里。Flyway 的初始化、命名规范和回滚策略,说白了就是三个字:规、矩、稳。命名规范是规矩,回滚策略是稳定。把这两件事做好,Schema 演进就不会出大乱子。