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。
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 |
可重复迁移,每次运行都会重新执行 |
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 手动回滚:最稳妥的方式
我个人最推荐的方式是手动回滚。流程是这样的:
- 写一个反向 SQL 脚本,手动在数据库里执行。
- 执行
flyway repair命令,清除 Flyway 的迁移记录。 - 删除或修改对应的 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 文件
这样做的好处是,你完全掌控了回滚过程,不会因为自动回滚的原子性问题导致数据不一致。
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,方便你理解。
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 演进就不会出大乱子。