主题
Flyway 指南
本指南循序渐进介绍 Flyway —— 数据库迁移(Migration)的事实标准。用"电商系统"的建表演进贯穿全文,每步只引入一个新概念。核心隐喻:Flyway 是数据库的 Git。 基于 Flyway 10.x / Spring Boot 3.x+。
目录
- 为什么需要 Flyway
- 核心概念三分钟速通
- 快速入门:三步让 Flyway 跑起来
- 迁移命名规范 —— Flyway 的"协议"
- 深入 flyway_schema_history —— 状态机的秘密
- Spring Boot 配置全解
- Java 迁移 —— 当 SQL 不够用时
- Baseline —— 给已有数据库"补票"
- 修复与排错
- CI/CD 集成 —— 让迁移自动化
- 速查清单
1. 为什么需要 Flyway
场景:三个开发者,同一个数据库
想象一下这个场景——你正在开发一个电商系统,团队有三个人:
第一天:
张三:在 user 表加了 age 字段 → ALTER TABLE user ADD COLUMN age INT;
李四:在 user 表加了 phone 字段 → ALTER TABLE user ADD COLUMN phone VARCHAR(20);
王五:创建了 order 表 → CREATE TABLE order (...);
第二天,张三要部署到测试环境:
- "李四,你的 phone 字段 SQL 在哪?"
- "王五,order 表建好了吗?"
- "等等,这些 SQL 的执行顺序是什么?哪个先跑?"
- "糟糕,我本地已经跑过了,测试环境跑重复了怎么办?"手动管理 SQL 脚本的四个痛点
① 顺序混乱:谁先跑、谁后跑,靠人记忆,部署时漏执行是常态
② 状态不可知:不知道某个 SQL 在某个环境是否已经执行过
③ 回滚困难:执行出错了,不知道从哪开始恢复
④ 版本黑洞:数据库当前"长什么样"没有记录,新人接手一脸懵Flyway 的答案
┌──────────────────────────────────────────────────────────────┐
│ Flyway = 数据库的 Git │
├──────────────────────────────────────────────────────────────┤
│ │
│ Git 管理代码 Flyway 管理数据库 │
│ ──────────────── ────────────────── │
│ git commit 快照 V1__xxx.sql 迁移文件 │
│ git log 历史 flyway_schema_history 表 │
│ git diff 变更 对比 checksum 检测篡改 │
│ 只提交变更部分 只执行未运行过的迁移 │
│ 团队共享历史 所有人看到的数据库结构一致 │
│ │
└──────────────────────────────────────────────────────────────┘一句话: Flyway 不帮你写 SQL,它帮你管理 SQL 的版本。每次数据库结构变更,写一个 SQL 文件,Flyway 自动按顺序执行、记录状态、防止重复。
2. 核心概念三分钟速通
迁移(Migration)是什么
一个迁移就是一次数据库结构变更。它可以是一个 SQL 文件,也可以是一段 Java 代码。每个迁移有唯一的版本号,Flyway 按版本号从小到大依次执行。
┌──────────────────────────────────────────────────────┐
│ 一次完整的迁移生命周期 │
├──────────────────────────────────────────────────────┤
│ │
│ 1. Flyway 启动,扫描 classpath:db/migration/ │
│ ↓ │
│ 2. 对比:哪些文件还没出现在 flyway_schema_history 中? │
│ ↓ │
│ 3. 按版本号排序,依次执行未执行的迁移 │
│ ↓ │
│ 4. 每执行一条,在 flyway_schema_history 中插入一条记录 │
│ ↓ │
│ 5. 所有迁移完成 → 应用启动 │
│ │
└──────────────────────────────────────────────────────┘flyway_schema_history 表
Flyway 在数据库中自动创建一张"账本",记录哪些迁移已经执行过:
sql
-- Flyway 自动创建和维护的表,你不需要手动操作
CREATE TABLE flyway_schema_history (
installed_rank INT, -- 执行顺序(1, 2, 3...)
version VARCHAR(50), -- 迁移版本号(1, 1.1, 2...)
description VARCHAR(200), -- 迁移描述(create_user_table)
type VARCHAR(20), -- 类型:SQL 或 JAVA
script VARCHAR(1000), -- 文件名
checksum INT, -- 文件内容的哈希值
installed_by VARCHAR(100), -- 谁执行的
installed_on TIMESTAMP, -- 执行时间
execution_time INT, -- 耗时(毫秒)
success BOOLEAN -- 是否成功
);这张表就是 Flyway 的"Git log"——任何时候你都能知道数据库当前处于哪个版本、谁在什么时候做了什么。
3. 快速入门:三步让 Flyway 跑起来
3.1 添加依赖
从 Flyway 10.0 开始,数据库特定支持被拆分为独立模块。使用 MySQL 需要同时引入 flyway-core 和 flyway-mysql:
xml
<!-- pom.xml -->
<!-- Flyway 核心 -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<!-- 版本由 Spring Boot Parent POM 统一管理,无需手动指定 -->
</dependency>
<!-- MySQL 数据库支持(Flyway 10+ 必须引入) -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>注意: 如果只引入
flyway-core而不引入flyway-mysql,启动时会报No database found to handle jdbc:mysql://...错误。 其他数据库同理,如 PostgreSQL 需要flyway-database-postgresql,SQL Server 需要flyway-sqlserver。
数据源配置(以 MySQL 为例):
yaml
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/ecommerce?useSSL=false&allowPublicKeyRetrieval=true
username: root
password: ${DB_PASSWORD:root}
driver-class-name: com.mysql.cj.jdbc.Driver注意: Flyway 依赖
DataSource,只要配好了数据源,Flyway 就能自动生效。
3.2 创建第一个迁移文件
在 src/main/resources/db/migration/ 目录下创建迁移文件:
src/main/resources/
└── db/
└── migration/
├── V1__create_user_table.sql
├── V2__create_product_table.sql
└── V3__add_email_to_user.sqlV1__create_user_table.sql —— 第一个迁移:
sql
-- V1:创建用户表
CREATE TABLE `user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` VARCHAR(50) NOT NULL COMMENT '用户名',
`password` VARCHAR(255) NOT NULL COMMENT '密码',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';V2__create_product_table.sql —— 第二个迁移:
sql
-- V2:创建商品表
CREATE TABLE `product` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` VARCHAR(200) NOT NULL COMMENT '商品名称',
`price` DECIMAL(10,2) NOT NULL COMMENT '价格',
`stock` INT NOT NULL DEFAULT 0 COMMENT '库存',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';V3__add_email_to_user.sql —— 给已有表加字段:
sql
-- V3:给用户表添加邮箱字段
ALTER TABLE `user` ADD COLUMN `email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱';3.3 启动项目,见证自动执行
启动 Spring Boot 应用,日志会显示 Flyway 的执行过程:
Flyway Community Edition 10.x.x by Redgate
Database: jdbc:mysql://localhost:3306/ecommerce (MySQL 8.0)
Successfully validated 3 migrations (execution time 00:00.015s)
Creating Schema History table `ecommerce`.`flyway_schema_history` ...
Current version of schema `ecommerce`: << Empty Schema >>
Migrating schema `ecommerce` to version "1 - create user table"
Migrating schema `ecommerce` to version "2 - create product table"
Migrating schema `ecommerce` to version "3 - add email to user"
Successfully applied 3 migrations to schema `ecommerce` (execution time 00:00.120s)此时查看数据库:
sql
SELECT version, description, installed_on, success
FROM flyway_schema_history
ORDER BY installed_rank;结果:
version | description | installed_on | success
────────┼────────────────────────┼─────────────────────┼────────
1 | create user table | 2026-08-04 10:00:01 | 1
2 | create product table | 2026-08-04 10:00:01 | 1
3 | add email to user | 2026-08-04 10:00:01 | 1再次启动应用,Flyway 发现没有新的迁移文件,直接跳过:
Current version of schema `ecommerce`: 3
Schema `ecommerce` is up to date. No migration necessary.4. 迁移命名规范 —— Flyway 的"协议"
Flyway 通过文件名来识别迁移的类型、版本和顺序。命名必须严格遵守规范。
4.1 版本化迁移(V)
格式:V{版本号}__{描述}.sql
↑ ↑↑
大写V 两个下划线规则:
- 版本号唯一,一旦执行就不能再修改文件内容
- Flyway 按版本号数字排序从小到大执行
- 同一个版本号只能出现一次
- 适用于表结构的增量变更
V1__create_user_table.sql ✅ 正确
V1.1__add_email_column.sql ✅ 正确(版本号可以是小数)
V20260804__create_order.sql ✅ 正确(用日期做版本号)
v1__init.sql ❌ 错误(小写 v)
V1_create_user.sql ❌ 错误(只有一个下划线)
V1__init.sql + V1__fix.sql ❌ 错误(版本号重复)4.2 可重复迁移(R)
格式:R__{描述}.sql
↑↑
大写R,两个下划线,没有版本号规则:
- 每次文件内容变化,Flyway 都会重新执行
- 没有版本号,每次执行后更新 checksum
- 适用于需要频繁修改的数据库对象
sql
-- R__create_product_view.sql
-- 每次修改这个视图定义,Flyway 都会重新执行
CREATE OR REPLACE VIEW v_product_summary AS
SELECT
p.id,
p.name,
p.price,
p.stock,
CASE WHEN p.stock > 0 THEN '有货' ELSE '售罄' END AS status
FROM product p;一句话: V 迁移像
git commit——记录一次不可变的历史;R 迁移像"总是保持最新"——每次改了就重新执行。
4.3 撤销迁移(U)
格式:U{版本号}__{描述}.sql
↑
大写U,与对应的 V 迁移版本号一致撤销迁移是 V 迁移的"反向操作",需要 Flyway Teams 版才能使用:
sql
-- V3__add_email_to_user.sql(正向)
ALTER TABLE `user` ADD COLUMN `email` VARCHAR(100);
-- U3__remove_email_from_user.sql(反向)
ALTER TABLE `user` DROP COLUMN `email`;社区版替代方案: 如果没有 Teams 版,采用"前滚"策略——出问题不撤销,而是写一个新的 V 迁移来修复。
4.4 版本号策略
两种主流策略,各有优劣:
策略 示例 优点 缺点
══════════════════════════════════════════════════════════════════════════
递增整数 V1, V2, V3...V47 简洁直观 多人同时开发易冲突
时间戳 V20260804.1 几乎不会冲突 版本号长,不直观
合并分支无压力推荐:个人项目/小团队用递增整数,多人协作/多分支用时间戳。
5. 深入 flyway_schema_history —— 状态机的秘密
这张表决定了 Flyway 的所有行为
┌─────────────────────────────────────────────────────────────────┐
│ flyway_schema_history 是 Flyway 的大脑 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Flyway 启动后问自己三个问题,全通过查这张表来回答: │
│ │
│ Q1: 这个迁移文件执行过吗? │
│ → SELECT * FROM flyway_schema_history WHERE version = '3' │
│ │
│ Q2: 上次执行后,文件内容被改过吗? │
│ → 对比文件的 checksum 和表中记录的 checksum │
│ │
│ Q3: 上次执行成功了吗? │
│ → 检查 success 字段 │
│ │
└─────────────────────────────────────────────────────────────────┘checksum 的陷阱
checksum 是 Flyway 最重要的保护机制——它不是版本号,而是文件内容的哈希:
场景:你已经执行了 V3__add_email.sql,Flyway 记录下 checksum = 12345
后来你不小心改了 V3__add_email.sql 的内容:
→ 新的 checksum = 67890
→ Flyway 发现 12345 ≠ 67890
→ 抛出异常:Migration checksum mismatch
→ 应用启动失败!
为什么?因为 Flyway 在保护你——如果允许"偷偷修改"已执行的迁移,
测试环境的数据库结构和生产环境就会不一致。这是最危险的数据库问题。正确做法: 永远不要修改已提交的迁移文件。要改结构,写一个新的 V 迁移。
6. Spring Boot 配置全解
6.1 基础配置
yaml
spring:
flyway:
# 迁移文件位置(默认值,通常不需要改)
locations: classpath:db/migration
# flyway_schema_history 表的名称(默认值)
table: flyway_schema_history
# SQL 文件编码
encoding: UTF-8
# 是否启用 Flyway(默认 true)
enabled: true
# 迁移执行前先校验(默认 true,生产环境强烈建议开启)
validate-on-migrate: true
# 发现待执行的迁移,但数据库非空时,是否自动创建 baseline
# 默认 false(见第 8 节 Baseline 详解)
baseline-on-migrate: false
# 迁移失败后,是否允许继续启动(默认 false,强烈不建议改)
fail-on-missing-locations: true
# SQL 语句分隔符
sql-migration-separator: ;
# 是否将所有迁移语句放在同一个事务中(默认 false)
# 注意:MySQL 的 DDL 语句会自动提交事务,此配置对 MySQL 的 DDL 无效
group: false
# 忽略未来版本(团队协作时可能用到)
ignore-migration-patterns: "*:ignored"6.2 环境隔离策略
不同环境,Flyway 的行为应该不同:
yaml
# application-dev.yml —— 开发环境:自动迁移
spring:
flyway:
enabled: true
clean-disabled: false # 允许 flyway:clean(清空数据库,仅开发用!)
# application-prod.yml —— 生产环境:手动审批
spring:
flyway:
enabled: true
clean-disabled: true # 严禁清空数据库生产环境的典型工作流:
┌─────────────────────────────────────────────────────────┐
│ 生产环境迁移流程 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 开发写好 V{n}__xxx.sql,提交到代码仓库 │
│ ↓ │
│ 2. 测试环境自动执行,验证迁移正确 │
│ ↓ │
│ 3. DBA / 运维 review 迁移 SQL │
│ ↓ │
│ 4. 生产部署时,Flyway 自动执行(或手动执行) │
│ ↓ │
│ 5. 验证 flyway_schema_history 一切正常 │
│ │
└─────────────────────────────────────────────────────────┘7. Java 迁移 —— 当 SQL 不够用时
大部分迁移用 SQL 就能搞定,但有时需要代码逻辑:
- 数据清洗:从旧表读取数据,转换后写入新表
- 批量更新:需要逐行计算,SQL 写起来太复杂
- 外部 API 调用:迁移过程中需要调用其他服务
java
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* V4:将用户密码从明文迁移为 BCrypt 加密
*
* Java 迁移的类名必须遵循 V{版本号}__{描述} 的命名规则,
* Flyway 通过类名识别版本号和执行顺序
*/
public class V4__encrypt_passwords extends BaseJavaMigration {
@Override
public void migrate(Context context) throws Exception {
// 查询所有用户
ResultSet users = context.getConnection()
.createStatement()
.executeQuery("SELECT id, password FROM user");
PreparedStatement update = context.getConnection()
.prepareStatement("UPDATE user SET password = ? WHERE id = ?");
while (users.next()) {
Long id = users.getLong("id");
String plainPassword = users.getString("password");
// 用 BCrypt 加密明文密码(BCrypt 需要引入 spring-security-crypto)
String encrypted = new org.springframework.security.crypto
.bcrypt.BCryptPasswordEncoder()
.encode(plainPassword);
update.setString(1, encrypted);
update.setLong(2, id);
update.executeUpdate();
}
}
}Java 迁移文件放在 src/main/java/db/migration/ 下(默认包路径为 db.migration):
src/main/java/
└── db/
└── migration/
└── V4__encrypt_passwords.java注意: Java 迁移类必须在 Flyway 能扫描到的包路径下。默认扫描
db.migration包,可通过locations配置修改。
原则: 能用 SQL 解决的,就用 SQL 迁移。SQL 迁移更简单、更可审计、更容易 review。只有确实需要程序逻辑时,才用 Java 迁移。
8. Baseline —— 给已有数据库"补票"
场景
你的电商系统已经上线跑了半年,数据库里有几百张表。
现在你想引入 Flyway,但 Flyway 要求从"空数据库"开始。
怎么办?Baseline 就是这个场景的解决方案。工作原理
┌──────────────────────────────────────────────────────┐
│ Baseline 工作原理 │
├──────────────────────────────────────────────────────┤
│ │
│ 设置 baseline-version = 1.0 │
│ 设置 baseline-on-migrate = true │
│ │
│ Flyway 启动时: │
│ 1. 发现数据库非空,且没有 flyway_schema_history 表 │
│ 2. 创建 flyway_schema_history 表 │
│ 3. 插入一条 baseline 记录: │
│ version = 1.0, type = BASELINE │
│ 4. 只执行版本号 > 1.0 的迁移 │
│ │
│ V0.5__xxx.sql → 跳过(版本号 ≤ 1.0) │
│ V1.0__xxx.sql → 跳过(等于 baseline) │
│ V1.1__xxx.sql → 执行 ✅ │
│ V2__xxx.sql → 执行 ✅ │
│ │
└──────────────────────────────────────────────────────┘配置
yaml
spring:
flyway:
baseline-on-migrate: true # 数据库非空时自动创建 baseline
baseline-version: 1.0 # 以此为起点,只执行更高版本的迁移
baseline-description: "现有数据库结构" # 记录在 history 表中的描述假设数据库已有一张 user 表,迁移文件如下:
V1__create_user_table.sql → CREATE TABLE user (...)
V2__create_product_table.sql → CREATE TABLE product (...)配置 baseline-version: 1.0 后,Flyway 的执行过程:
1. 创建 flyway_schema_history 表
2. 插入 baseline 记录:version = 1.0, type = BASELINE
3. V1(版本号 1 ≤ baseline 1.0)→ 跳过,不执行 CREATE TABLE user ✅
4. V2(版本号 2 > baseline 1.0)→ 执行 CREATE TABLE product ✅flyway_schema_history 最终状态:
installed_rank | version | type | description | success
───────────────┼─────────┼──────────┼───────────────────────┼────────
1 | 1.0 | BASELINE | 现有数据库结构 | 1
2 | 2 | SQL | create product table | 1关键点: V1 的
CREATE TABLE user根本没有执行,所以不会报"表已存在"的错误。Baseline 本质上就是你对 Flyway 说:"数据库里已有的东西,等价于我执行到版本 1.0 的状态,你从这之后开始管。"
9. 修复与排错
错误 1:checksum mismatch
错误信息:
Migration checksum mismatch for migration version 3
→ Expected: 12345
→ Actual: 67890原因: 你已经执行过 V3,但后来修改了 V3 的文件内容。Flyway 检测到 checksum 不匹配,拒绝启动。
修复方案:
1. 用 git checkout 恢复 V3 的原始内容
2. 新建 V4__xxx.sql,把你想做的额外修改写在 V4 里
3. 正常启动,Flyway 执行 V4错误 2:迁移执行失败
错误信息:
Migration V4__add_status_column.sql failed
→ SQL State : 42S21
→ Error Code : 1060
→ Message : Duplicate column name 'status'修复步骤:
1. 不要慌,Flyway 在 flyway_schema_history 中记录了 success = false
2. 修复 SQL 脚本中的错误
3. 手动删除 flyway_schema_history 中失败的记录:
DELETE FROM flyway_schema_history WHERE success = 0;
4. 重新启动应用
5. Flyway 会重新执行这个迁移错误 3:找不到迁移文件
错误信息:
Cannot find migration: V3__add_email.sql原因: 数据库中记录了 V3 已执行,但文件系统中找不到对应的 SQL 文件。
修复:
- 如果文件被误删:从 Git 历史恢复
- 如果文件改名了:改名不行,Flyway 靠文件名匹配。需要手动更新 history 表
错误 4:版本号重复
错误信息:
Found more than one migration with version 4
→ V4__create_order.sql
→ V4__fix_user.sql原因: 两个迁移文件使用了相同的版本号。常见于多人协作时,不同开发者在同一分支都用了 V4。
修复:
1. 确认哪个 V4 是“正确的”(或两个都需要保留)
2. 将其中一个重命名为更高的版本号,如 V5__fix_user.sql
3. 如果该版本号已经执行过,需要删除 history 表中的旧记录:
DELETE FROM flyway_schema_history WHERE version = '4';
4. 重新启动应用预防: 多人协作时,使用时间戳版本号(如 V20260804.1、V20260804.2)可以从根本上避免冲突。详见第 4.4 节。
错误 5:数据库非空但未启用 Baseline
错误信息:
Found non-empty schema(s) but no schema history table!
Set baseline-on-migrate or run flyway baseline command.原因: 数据库已有表结构,但没有 flyway_schema_history 表,且未开启 baseline-on-migrate。Flyway 无法判断哪些结构是“已有的”、哪些迁移是“已执行的”,为保护数据安全,拒绝启动。
修复:
yaml
spring:
flyway:
baseline-on-migrate: true # 允许 Flyway 在非空库上创建 baseline
baseline-version: 1.0 # 设为当前数据库结构对应的最高迁移版本号注意:
baseline-version必须 ≥ 所有已存在数据库结构对应的最高迁移版本号,否则 Flyway 会尝试重复执行已有的迁移,导致“表已存在”等报错。详见第 8 节。
10. CI/CD 集成 —— 让迁移自动化
在实际项目中,Flyway 迁移应该集成到 CI/CD 流程中,而不是手动执行。以下是几种常见的集成方式。
10.1 应用启动时自动迁移(默认)
Spring Boot 集成 Flyway 后,应用启动时会自动执行未运行的迁移。这是最简单的方式,适合大多数场景:
部署流程:
1. 代码合并到 main 分支
2. CI 构建镜像(包含新的迁移文件)
3. 部署新镜像 → 应用启动 → Flyway 自动执行迁移
4. 迁移成功 → 应用正常运行
迁移失败 → 应用启动失败,不会带病上线优点: 零额外配置,迁移和应用部署绑定,不会出现“代码上了但迁移没跑”的情况。 风险: 如果多个实例同时启动,可能并发执行迁移。可通过配置
spring.flyway.enabled=false关闭非主节点的 Flyway,或使用数据库锁机制。
10.2 CI/CD 流水线中独立执行迁移
对于生产环境,更推荐将迁移作为独立的流水线步骤,在应用部署之前执行。这样可以做到:迁移失败时不部署应用,DBA(数据库管理员) 可以先 review 迁移 SQL。
GitHub Actions 示例:
yaml
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
migrate:
name: Run Flyway Migration
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Flyway Migration
run: |
mvn flyway:migrate \
-Dflyway.url=${{ secrets.DB_URL }} \
-Dflyway.user=${{ secrets.DB_USER }} \
-Dflyway.password=${{ secrets.DB_PASSWORD }}
deploy:
name: Deploy Application
needs: migrate # 迁移成功后才部署
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: echo "Deploying application..."GitLab CI 示例:
yaml
# .gitlab-ci.yml
flyway-migrate:
stage: deploy
image: maven:3.9-eclipse-temurin-17
script:
- mvn flyway:migrate
-Dflyway.url=$DB_URL
-Dflyway.user=$DB_USER
-Dflyway.password=$DB_PASSWORD
only:
- main
deploy-app:
stage: deploy
needs: [flyway-migrate] # 迁移成功后才部署
script:
- echo "Deploying application..."10.3 生产环境迁移的最佳实践
┌─────────────────────────────────────────────────────────────┐
│ 生产环境迁移流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 开发者写 V{n}__xxx.sql,提交到代码仓库 │
│ ↓ │
│ 2. CI 流水线自动执行迁移(测试环境数据库) │
│ → 验证迁移 SQL 的正确性 │
│ ↓ │
│ 3. DBA / 运维 review 迁移 SQL │
│ → 检查是否有锁表风险、大表 DDL 等 │
│ ↓ │
│ 4. 生产部署流水线:先执行迁移,再部署应用 │
│ → 迁移失败则中止部署,不会带病上线 │
│ ↓ │
│ 5. 验证 flyway_schema_history 一切正常 │
│ │
└─────────────────────────────────────────────────────────────┘关键原则:
- 迁移和应用部署分离 —— 迁移失败时不部署应用,避免“代码上了但数据库没准备好”
- 敏感信息用 Secrets 管理 —— 数据库密码永远不要写在流水线配置文件中
- 测试环境先跑一遍 —— 生产迁移前,确保在测试环境已验证通过
- 大表 DDL 要谨慎 —— MySQL 对大表的
ALTER TABLE可能锁表,建议在低峰期执行,或使用pt-online-schema-change等工具
11. 速查清单
11.1 命名规范速查
V{版本号}__{描述}.sql 版本化迁移,执行一次,不可修改
R__{描述}.sql 可重复迁移,内容变化时重新执行
U{版本号}__{描述}.sql 撤销迁移,Teams 版可用11.2 配置速查
yaml
spring:
flyway:
enabled: true # 是否启用
locations: classpath:db/migration # 迁移文件路径
table: flyway_schema_history # 历史表名
baseline-on-migrate: false # 非空库自动 baseline
baseline-version: 1.0 # baseline 版本号(与第 8 节一致)
validate-on-migrate: true # 执行前校验
clean-disabled: true # 禁用 clean(生产必须)
encoding: UTF-8 # 文件编码
out-of-order: false # 是否允许乱序执行11.3 数据库状态判断
flyway_schema_history 不存在 → 空数据库,从头执行所有迁移
flyway_schema_history 存在,但版本 < 最新迁移文件 → 有待执行的迁移
flyway_schema_history 存在,版本 = 最新迁移文件 → 数据库已是最新
flyway_schema_history 中存在 success = false → 有失败的迁移,需要修复
checksum 不匹配 → 已执行的迁移文件被修改了,需要检查11.4 常见陷阱
陷阱 现象 解决方案
══════════════════════════════════════════════════════════════════════════════════
修改已执行的迁移文件 checksum mismatch 报错 写新迁移,不要改旧的
版本号重复 Found more than one migration 确保版本号唯一
只有一个下划线(V1_desc.sql) 迁移被忽略,不执行 使用双下划线 V1__desc.sql
在事务中混合 DDL 和 DML(MySQL) 迁移执行失败 MySQL 的 DDL 会自动提交
事务,注意执行顺序
生产环境未禁用 clean 误操作清空整个数据库 设置 clean-disabled: true
多分支同时开发,版本号冲突 合并时迁移顺序混乱 使用时间戳版本号11.5 Flyway vs Liquibase
Flyway Liquibase
═════════════════════════════════════════════════════════════
迁移格式 纯 SQL XML / YAML / JSON / SQL
学习成本 低(会写 SQL 就会用) 较高(需要学 DSL 语法)
回滚支持 Teams 版(付费) 内置支持(免费)
数据库无关性 弱(SQL 语法随数据库变化) 强(DSL 自动转换方言)
社区活跃度 更活跃 活跃
适用场景 团队都用同一种数据库 需要支持多种数据库
追求简单和 SQL 可控性 需要免费回滚功能11.6 选择指南
小团队、单一数据库、追求简单 → Flyway 社区版
需要免费回滚、多数据库支持 → Liquibase
需要回滚 + 简单 → Flyway Teams 版(付费)最后: Flyway 的本质是让你的数据库和代码一样,拥有版本历史。代码有 Git,数据库有 Flyway。每次写 SQL 变更时,问自己一个问题:"这个迁移,从空数据库开始能跑通吗?" 如果能,Flyway 就帮你搞定剩下的一切。