Flyway 数据库版本管理工具使用指南

2 阅读9分钟

Flyway 数据库版本管理工具使用指南

#springboot #java

一、为什么需要 Flyway

在引入 Flyway 之前,数据库变更依赖人工执行 SQL,存在以下问题:

  • 上线时忘记执行 SQL,导致应用报错
  • 同一条 SQL 被重复执行,破坏数据结构
  • 各环境数据库结构不一致,测试通过但生产报错
  • 没有记录哪些 SQL 执行过、什么时候执行的

Flyway 的作用:让数据库结构变更像代码一样被版本管理,应用启动时自动执行还未执行过的 SQL,已执行过的绝不重复执行。


二、工作原理

Flyway 启动时做三件事:

① 扫描 resources/db/migration/ 目录下所有 SQL 文件
② 查询数据库中的 flyway_schema_history 表,确认哪些已执行
③ 按版本号顺序,只执行尚未执行的文件

flyway_schema_history 是 Flyway 自动创建和维护的记录表,无需手动创建:

+---------+---------------------------+---------+---------------------+
| version | description               | success | installed_on        |
+---------+---------------------------+---------+---------------------+
| 1.0.1   | init                      |    1    | 2026-01-10 10:00:00 |
| 1.0.2.1 | add user ext table        |    1    | 2026-02-15 14:30:00 |
| 1.0.2.2 | add order pay column      |    1    | 2026-02-15 14:30:01 |
| 1.0.2.3 | add index order status    |    1    | 2026-02-15 14:30:01 |
| 1.0.2.4 | dict payment type         |    1    | 2026-02-15 14:30:02 |
+---------+---------------------------+---------+---------------------+

三、项目集成

3.1 添加 Maven 依赖

pom.xml 中添加:

<!-- Flyway 核心 -->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>

<!-- MySQL 支持(使用 MySQL 必须加) -->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>
</dependency>

Spring Boot 2.7+ 已内置 Flyway 版本管理,无需指定版本号。

3.2 application.yml 配置

spring:
  flyway:
    enabled: true                        # 开启 Flyway
    locations: classpath:db/migration    # SQL 文件目录
    baseline-on-migrate: true            # 老项目首次接入时不报错,新项目可不加
    validate-on-migrate: true            # 启动时校验文件完整性,强烈建议开启
    table: flyway_schema_history         # 记录表名(默认值,可不写)

各环境数据库连接各自配置,Flyway 会自动作用于对应的数据库,无需额外区分。

3.3 目录结构

src/main/resources/
  db/
    migration/
      V1.0.1__init.sql
      V1.0.2.1__add_user_ext_table.sql
      V1.0.2.2__add_order_pay_column.sql
      V1.0.2.3__add_index_order_status.sql
      V1.0.2.4__dict_payment_type.sql
      R__recreate_view_order_summary.sql

四、文件命名规范

4.1 完整格式

{前缀}{版本号}__{描述}.sql

示例:V1.0.2.1__add_user_ext_table.sql

4.2 前缀说明

前缀名称行为适用场景
V版本化迁移只执行一次,执行后不再重复建表、加字段、加索引、数据初始化
R可重复迁移文件内容变化就重新执行视图、存储过程、函数
U撤销迁移对应 V 文件的回滚脚本社区版不支持,了解即可

4.3 版本号规则

版本号中的单下划线 _ 会被自动替换为点 .,以下两种写法完全等价:

V1.0.2.1__add_user_table.sql   ✅
V1_0_2_1__add_user_table.sql   ✅(等价写法)

建议统一使用点号,更直观。

4.4 分隔符

两个下划线 __ 是版本号和描述的分隔符,一个下划线是版本号内部的分隔,不要混淆:

V1.0.2.1__add_user_table.sql
─────────  ───────────────
版本号      描述(单下划线会显示为空格)

V1.0.2_add_user_table.sql    ❌ 错误:Flyway 会把描述也当成版本号一部分

4.5 描述规则

描述中的单下划线会在记录表中显示为空格,只影响可读性,不影响执行:

文件名                              history 表 description 显示
V1.0.2.2__add_order_pay_column  →  add order pay column
R__recreate_view_order_summary  →  recreate view order summary

五、同一版本多类型变更

一个版本内有多种类型的变更时,用小版本号区分执行顺序,严格按以下顺序排列:

V1.0.2.1__xxx.sql   ← 第一步:新建表
V1.0.2.2__xxx.sql   ← 第二步:现有表加字段
V1.0.2.3__xxx.sql   ← 第三步:加索引(依赖字段存在)
V1.0.2.4__xxx.sql   ← 第四步:字典 / 初始化数据(依赖表和字段存在)

顺序不能乱,索引依赖字段,数据依赖表结构,写错顺序启动直接报错。


六、SQL 编写规范

6.1 建表:加 IF NOT EXISTS

-- V1.0.2.1__add_user_ext_table.sql
CREATE TABLE IF NOT EXISTS `sys_user_ext` (
  `id`         BIGINT       NOT NULL AUTO_INCREMENT,
  `user_id`    BIGINT       NOT NULL COMMENT '关联用户ID',
  `nickname`   VARCHAR(64)  DEFAULT NULL COMMENT '昵称',
  `avatar`     VARCHAR(255) DEFAULT NULL COMMENT '头像URL',
  `created_at` DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户扩展信息';

6.2 加字段:先判断列是否存在

MySQL 8.0 以下不支持 ADD COLUMN IF NOT EXISTS,用存储过程判断:

-- V1.0.2.2__add_order_pay_column.sql
DROP PROCEDURE IF EXISTS add_column_if_not_exists;
DELIMITER //
CREATE PROCEDURE add_column_if_not_exists()
BEGIN
  IF NOT EXISTS (
    SELECT 1 FROM information_schema.COLUMNS
    WHERE TABLE_SCHEMA = DATABASE()
      AND TABLE_NAME   = 't_order'
      AND COLUMN_NAME  = 'pay_channel'
  ) THEN
    ALTER TABLE `t_order`
      ADD COLUMN `pay_channel`    VARCHAR(32)    DEFAULT NULL    COMMENT '支付渠道' AFTER `pay_type`,
      ADD COLUMN `refund_amount`  DECIMAL(10,2)  DEFAULT 0.00    COMMENT '退款金额' AFTER `pay_channel`;
  END IF;
END //
DELIMITER ;
CALL add_column_if_not_exists();
DROP PROCEDURE IF EXISTS add_column_if_not_exists;

6.3 加索引:先判断索引是否存在

-- V1.0.2.3__add_index_order_status.sql
DROP PROCEDURE IF EXISTS add_index_if_not_exists;
DELIMITER //
CREATE PROCEDURE add_index_if_not_exists()
BEGIN
  IF NOT EXISTS (
    SELECT 1 FROM information_schema.STATISTICS
    WHERE TABLE_SCHEMA = DATABASE()
      AND TABLE_NAME   = 't_order'
      AND INDEX_NAME   = 'idx_status_created'
  ) THEN
    ALTER TABLE `t_order` ADD INDEX `idx_status_created` (`status`, `created_at`);
  END IF;
END //
DELIMITER ;
CALL add_index_if_not_exists();
DROP PROCEDURE IF EXISTS add_index_if_not_exists;

6.4 字典 / 初始化数据:保证幂等

-- V1.0.2.4__dict_payment_type.sql
-- 用 INSERT IGNORE 避免重复插入报错
INSERT IGNORE INTO `sys_dict_item` (`dict_code`, `item_value`, `item_label`, `sort`) VALUES
  ('PAYMENT_TYPE', 'WECHAT',  '微信支付', 1),
  ('PAYMENT_TYPE', 'ALIPAY',  '支付宝',   2),
  ('PAYMENT_TYPE', 'BALANCE', '余额支付', 3);

6.5 视图 / 存储过程:用 R 前缀

-- R__recreate_view_order_summary.sql
-- 视图用 CREATE OR REPLACE,文件内容修改后 Flyway 自动重新执行
CREATE OR REPLACE VIEW `v_order_summary` AS
SELECT
  u.name            AS user_name,
  COUNT(o.id)       AS order_count,
  SUM(o.amount)     AS total_amount
FROM `t_order` o
JOIN `sys_user` u ON o.user_id = u.id
GROUP BY u.id, u.name;

七、铁律:已提交的 V 文件绝对不能修改

Flyway 对每个 V 文件计算 checksum,修改已执行过的文件内容,下次启动会报错

Validate failed: Migration checksum mismatch for migration version 1.0.2.1
Expected: 112233445  Resolved: 998877665

✅ 正确做法:新建更高版本的文件来修正

# 发现 V1.0.2.1 建表时漏加了字段
# ❌ 错误:直接修改 V1.0.2.1__add_user_ext_table.sql
# ✅ 正确:新建文件

V1.0.2.5__fix_user_ext_add_remark.sql   ← 用新文件补充

R 文件(可重复迁移)可以随意修改,这是它存在的意义。


八、多人协作版本号冲突问题

多人同时开发时,最容易出现两个人都创建了 V1.0.2.x 的文件,合并时 Flyway 报版本冲突。

推荐方案:使用时间戳作为版本号

V20260324141500__add_user_ext_table.sql
V20260324153020__add_order_pay_column.sql

时间戳精确到秒,绝对不会冲突,强烈推荐多人并行开发时使用。


九、与 CI/CD 流程的配合

结合项目现有的 GitLab CI/CD 流程,无需在流水线中单独处理数据库变更,Flyway 在应用启动时自动执行。

dev 提交代码(含新的 V1.0.2.x__.sql)
  └─ CI 构建镜像,部署到开发环境
       └─ 容器启动 → Flyway 自动执行新 SQL → 开发库结构更新 ✅

MR 合并到 release → 部署测试环境
  └─ 容器启动 → Flyway 自动执行新 SQL → 测试库结构更新 ✅

MR 合并到 main → 手动触发部署生产
  └─ 容器启动 → Flyway 自动执行新 SQL → 生产库结构更新 ✅

SQL 文件跟着代码一起走,同一版本的代码和数据库结构永远对齐,不再出现代码升级了但数据库忘改的情况。


十、常见问题

Q:老项目已有数据库,第一次接入 Flyway 怎么办?

确保 application.yml 中配置了 baseline-on-migrate: true,Flyway 会把当前数据库状态标记为基线,只执行基线之后的新文件,不会尝试重新执行历史 SQL。


Q:启动报 checksum mismatch 怎么处理?

说明有人修改了已执行过的 V 文件,找到被修改的文件,将内容恢复原样(git 回退),然后用新版本文件来实现需要的变更。


Q:想回滚某次数据库变更怎么办?

新建一个更高版本的文件,手动写回滚 SQL(如 DROP COLUMNDROP TABLE)。Flyway 社区版不支持自动回滚,生产环境回滚数据库变更需谨慎评估,建议提前准备好回滚脚本。


Q:本地开发时频繁改表结构,要一直新建 V 文件吗?一次迭代改了 10 次难道要建 10 个文件?

不需要。V 文件记录的是"变更结果",不是"变更过程"。 本地开发分两个阶段来处理:

开发阶段(功能未完成):本地直接用 Navicat / DBeaver 等工具随意改表结构,同时关闭本地的 Flyway,不产生任何 V 文件:

# application-local.yml  本地专用配置,不提交到代码仓库
spring:
  flyway:
    enabled: false   # 开发期间关掉,随便改表结构

提 MR 前(功能完成):将本次所有数据库变更整理归纳成最终结果,写成一个或几个干净的 V 文件一起提交。不管本地改了几次,对外只体现最终状态:

# 本地反复改了 10 次字段,但 MR 里只提交最终版本:
V1.0.5.1__add_product_table.sql        ← 建表(最终结构)
V1.0.5.2__add_order_status_column.sql  ← 加字段(最终版本)
V1.0.5.3__add_index_product_name.sql   ← 加索引

这和写代码是一样的道理:本地可以反复修改,但合并到分支的是整理好的最终结果,不是每一次中间过程的记录。


Q:菜单路由、字典数据之前是在页面维护再同步到其他环境,改用 Flyway 管会重复插入吗?

这类数据要区分两种情况分别处理:

情况一:系统内置数据,上线后不在页面改(如内置字典类型、系统默认菜单)

适合放 V 文件,随代码一起发布。用 INSERT IGNORE 写法保证幂等,即使意外重复执行也不会报错:

-- V1.0.5.4__init_menu_data.sql
INSERT IGNORE INTO `sys_menu` (`id`, `name`, `path`, `component`) VALUES
  (1001, '用户管理', '/user', 'user/index'),
  (1002, '角色管理', '/role', 'role/index');

INSERT IGNORE INTO `sys_dict_item` (`dict_code`, `item_value`, `item_label`) VALUES
  ('ORDER_STATUS', '0', '待支付'),
  ('ORDER_STATUS', '1', '已支付');

正常情况下 Flyway 根本不会重复执行 V 文件,INSERT IGNORE 只是双重保险。

情况二:各环境数据不一致,由运营 / 管理员在页面配置维护的数据

这类数据不适合放 Flyway,强行用 SQL 管反而容易覆盖掉各环境独立维护的数据。建议:

  • 只管新增:新版本新加了一个菜单项,写 V 文件只插入这一条新记录,不动存量数据
  • 存量继续发布单:已有的业务数据继续通过现有发布单流程同步,两者互不干扰

一句话原则:Flyway 管好结构变更和系统初始数据就够了,不要试图用它接管所有数据。


附一:各场景处理方式速查

场景推荐做法
本地开发频繁改表结构本地关闭 Flyway,提 MR 前整理成干净的 V 文件
系统内置字典、初始菜单V 文件 + INSERT IGNORE,随代码发布
运营在页面维护的业务数据只写新增部分的 V 文件,存量继续发布单
视图、存储过程R 文件,内容可随时修改,自动重新执行
多人并行开发版本号改用时间戳,避免冲突

附二:文件命名速查

V{版本号}__{描述}.sql      版本化迁移,只执行一次
R__{描述}.sql              可重复迁移,内容变了就重新执行
─────────────────────────────────────────────────
V1.0.1__init.sql                        初始化建表
V1.0.2.1__add_xxx_table.sql             新建表
V1.0.2.2__add_xxx_column.sql            加字段
V1.0.2.3__add_idx_xxx.sql               加索引
V1.0.2.4__dict_xxx.sql                  字典数据
R__recreate_view_xxx.sql                视图
R__recreate_proc_xxx.sql                存储过程