Flyway数据库版本管理实战——从入门到项目落地大家好我是黒漂技术佬。今天聊一个很多团队出事之后才想起来的话题——数据库版本管理。你有没有遇到过这种情况开发环境数据库跑得好好的一到测试环境就报Unknown column xxx或者新同事拉下代码跑不起来因为少了一张表再或者上线的 SQL 脚本到底执行了没有谁也说不清楚这些问题的根源都一样代码有 Git 管版本数据库脚本却是野生的。Flyway 就是来解决这个问题的。一、Flyway 是什么一句话Flyway 是数据库的 Git。它在数据库中维护一张flyway_schema_history表记录每一次数据库变更的版本号、脚本名、校验和、执行时间。每次启动时Flyway 扫描你的迁移脚本目录和历史表一对比——没执行过的就执行执行过但被篡改的就报错干干净净。┌──────────────────────────────────────────────────┐ │ Flyway 工作流程 │ │ │ │ 启动 → 扫描 migrations 目录 → 读取历史表 │ │ → 对比版本号 → 执行未执行的脚本 → 更新历史表 │ │ → 校验已有脚本完整性 → 应用启动 │ └──────────────────────────────────────────────────┘二、为什么需要 Flyway2.1 没有版本管理的痛苦拿我们无人售货柜项目来说数据库里有订单表、设备表、商品表、支付表等几十张表。后端是用 SpringBoot MyBatis-Plus 做的开发过程中表结构变更是家常便饭今天给device表加个firmware_version字段明天给order表加个refund_reason字段后天发现goods表缺个索引查询慢得要命在没有 Flyway 的时候流程是这样的“老张我刚改了表结构SQL 发你了待会部署的时候跑一下。”老张“收到。”三天后上线。用户反馈货柜扫码不出商品。查了半天——老张的 SQL 跑在了测试库生产库没跑。这是真实发生过的事故。从那以后我们所有的 SQL 变更都走 Flyway 管理。2.2 Flyway 解决的核心问题问题Flyway 如何解决SQL 脚本漏执行启动自动扫描未执行的自动补上脚本重复执行已执行的脚本不会重复跑脚本被篡改校验和checksum不一致直接报错多人协作冲突版本号唯一冲突在开发阶段就能发现环境不一致同一套脚本开发/测试/生产执行结果一致回滚困难配合 Undo 迁移商业版或手动写逆向脚本三、核心概念迁移脚本Flyway 的迁移脚本有三种类型靠文件名前缀区分3.1 版本化迁移Versioned Migration——V这是最常用的类型文件名格式V版本号__描述.sql注意版本号后面的分隔符是两个下划线不是单个。-- V1__init_database.sqlCREATETABLEdevice(idBIGINTPRIMARYKEYAUTO_INCREMENT,device_codeVARCHAR(32)NOTNULLCOMMENT设备编号,statusTINYINTDEFAULT0COMMENT状态0-离线 1-在线 2-故障,create_timeDATETIMEDEFAULTCURRENT_TIMESTAMP)COMMENT设备表;-- V2__add_firmware_version.sqlALTERTABLEdeviceADDCOLUMNfirmware_versionVARCHAR(16)DEFAULT1.0.0COMMENT固件版本号;版本号规则数字递增不能重复不能跳号跳号不会报错但你会后悔已执行的版本号绝对不能修改——改版本号等于告诉 Flyway “这是新脚本”它会尝试再执行一遍团队协作时先到先得——谁先合代码谁占住版本号3.2 撤销迁移Undo Migration——U商业版才支持文件名与对应的 V 脚本一一对应-- U2__remove_firmware_version.sqlALTERTABLEdeviceDROPCOLUMNfirmware_version;社区版没有这个功能但可以通过写V脚本来做回滚操作——不推荐因为版本号不可逆。社区版的最佳实践是只向前不后退。真要回滚就写一个新的V脚本把数据改回来。3.3 可重复迁移Repeatable Migration——R文件名格式R__描述.sql和版本化迁移最大的区别内容变了就会重新执行。Flyway 用 checksum 来判断是否需要重新运行。适用场景视图定义CREATE OR REPLACE VIEW存储过程、函数需要始终保持最新状态的触发器-- R__device_overview_view.sqlCREATEORREPLACEVIEWdevice_overviewASSELECTd.device_code,d.status,d.firmware_version,COUNT(o.id)AStoday_ordersFROMdevice dLEFTJOINorderoONd.ido.device_idANDDATE(o.create_time)CURDATE()GROUPBYd.id;每次改这个视图定义Flyway 检测到 checksum 变了就会重新执行这个脚本。四、flyway_schema_history一切的核心Flyway 在数据库中维护一张元数据表默认叫flyway_schema_history这张表就是一切魔法的基础。CREATETABLEflyway_schema_history(installed_rankINTNOTNULL,-- 执行顺序versionVARCHAR(50),-- 版本号V 脚本有R 脚本为 NULLdescriptionVARCHAR(200)NOTNULL,-- 描述文件名中的描述部分typeVARCHAR(20)NOTNULL,-- 类型SQL / JDBCscriptVARCHAR(1000)NOTNULL,-- 脚本文件名checksumINT,-- 校验和installed_byVARCHAR(100)NOTNULL,-- 执行者数据库用户名installed_onTIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMP,execution_timeINTNOTNULL,-- 执行耗时毫秒successTINYINT(1)NOTNULL-- 是否成功);每次启动Flyway 做三件事扫描classpath:db/migration目录下所有符合命名规则的.sql文件对比文件版本号与flyway_schema_history表中已记录的版本号执行未执行的新脚本并将执行结果写入历史表如果发现已执行的脚本 checksum 与记录不一致 →直接抛异常应用启动失败。这是故意的——Flyway 宁可让你启动不了也不让数据出问题。五、SpringBoot 集成 FlywaySpringBoot 对 Flyway 的支持是开箱即用的引入依赖写好脚本就完事了。5.1 添加依赖!-- pom.xml --dependencygroupIdorg.flywaydb/groupIdartifactIdflyway-core/artifactId/dependencydependencygroupIdorg.flywaydb/groupIdartifactIdflyway-mysql/artifactId/dependencySpringBoot 会自动配置 Flyway因为 spring-boot-starter 里已经包含了对 flyway 的自动配置。你只需要确保flyway-core在 classpath 上就行。5.2 放置迁移脚本在src/main/resources/db/migration/目录下按序放置 SQL 文件src/main/resources/ └── db/ └── migration/ ├── V1__init_database.sql ├── V2__add_firmware_version.sql ├── V3__create_order_table.sql ├── V4__add_order_index.sql └── R__device_overview_view.sql5.3 配置文件# application.ymlspring:flyway:enabled:truelocations:classpath:db/migration# 如果数据库不是空的有历史数据需要设置基线版本baseline-on-migrate:truebaseline-version:1# 禁止在事务中执行MySQL DDL 不支持事务回滚# 注意新版 Flyway 默认根据数据库类型自动判断一般不需要手动设# 编码encoding:UTF-8# 如果脚本校验和不匹配是否允许启动生产环境坚决设 falsevalidate-on-migrate:true重点解释几个参数baseline-on-migrate: true这个是给已有数据库用的。假设你的项目已经跑了半年数据库里有一堆表现在要接入 Flyway。此时 Flyway 看到数据库中已有device表而你的V1__init_database.sql脚本里恰好是CREATE TABLE device这不就冲突了吗开启baseline-on-migrate后Flyway 会把当前数据库状态标记为baseline-version比如 V1之后只执行 V2 及以后的脚本。已有的表 Flyway 不动当它是 V1 的产物。validate-on-migrate: true启动时校验已执行脚本的 checksum 是否一致。生产环境必须开不然有人偷偷改了脚本你还不知道。5.4 程序化配置高级用法如果需要在代码里动态控制 Flyway 行为ConfigurationpublicclassFlywayConfig{BeanpublicFlywayMigrationStrategyflywayMigrationStrategy(){returnflyway-{// 启动前先 repair修复 checksum慎用// flyway.repair();// 执行迁移flyway.migrate();// 打印迁移结果MigrationInfoServiceinfoflyway.info();MigrationInfocurrentinfo.current();if(current!null){log.info(当前数据库版本{},current.getVersion().getVersion());}log.info(所有迁移脚本已执行);};}}六、实战踩坑与最佳实践6.1 开发流程团队开发中正确的流程是拉最新代码包括最新的 migration 脚本如果需要改数据库创建新的V脚本版本号取当前最大版本号 1本地跑一遍确认脚本无误提交代码先提 PR不要绕过合并后CI/CD 自动执行迁移关键原则已合并到主分支的 V 脚本绝对不能再修改。如果要改表结构新建一个 V 脚本写 ALTER 语句。6.2 版本号冲突两人同时新建 V5合并时必然冲突。解决方法合并代码时后合并的人把版本号改成 V6但要注意如果 V5 脚本引用了 V4 的结构V6 也得在 V4 之后执行顺序要保持团队规范一人一次只占一个版本号预留给其他同事6.3 Checksum 不匹配常见于以下场景有人直接改了已执行过的 SQL 脚本文件不管空格还是注释都算数据库编码不一致导致 checksum 计算偏差从 Windows 换到 Linux换行符不同解决方式按推荐度排序最佳改回去新建一个 V 脚本来做变更临时开发环境执行flyway repair重置 checksum生产环境禁止极端情况手动更新flyway_schema_history表的 checksum 字段不建议除非你清楚后果6.4 多模块项目一个 SpringCloud 微服务项目多个模块各有自己的数据库怎么管理方案一多数据源 多 Flyway 实例推荐BeanpublicFlywayorderFlyway(Qualifier(orderDataSource)DataSourceds){returnFlyway.configure().dataSource(ds).locations(classpath:db/migration/order).table(flyway_schema_history_order)// 自定义历史表名避免冲突.load();}方案二一个 Flyway 管理多个 schema不太推荐耦合度高spring:flyway:schemas:order_db,device_db,payment_db6.5 和 Liquibase 的对比维度FlywayLiquibase迁移脚本格式纯 SQLXML / YAML / JSON / SQL学习成本低你会写 SQL 就会用中需要学 Liquibase DSL灵活性中等高支持条件逻辑、回滚、多环境社区规模大大SpringBoot 集成开箱即用开箱即用简单说如果团队只会 SQL选 Flyway如果需要复杂的分支合并、多环境差异化配置选 Liquibase。对我们无人售货柜项目来说Flyway 完全够用——我们不需要 rollback 到任意版本只要保证每次 SQL 变更都被可靠地执行一次就够了。6.6 无人售货柜项目中的 Flyway 实践我们项目上线时的数据库版本演进V1 → 初始化device 设备表、goods 商品表 V2 → 新增 firmware_version 固件版本字段 V3 → 创建 order 订单表含支付状态、设备关联 V4 → 加索引idx_device_code、idx_order_create_time V5 → 新增 refund 退款表 V6 → order 表加 refund_reason 字段 V7 → 创建 ai_recognition_result 视觉识别结果表 V8 → 加复合索引优化识别查询性能 V9 → 新增 device_heartbeat 心跳表MQTT 设备在线管理 V10 → goods 表加 temperature_range 温控配置字段 ...每次迭代DBA其实就我自己写好迁移脚本放进db/migration目录代码一合CI 流水线跑完自动部署数据库和代码始终保持同步。再也没出现过SQL 忘了执行的事故。七、总结Flyway 不是什么高深的技术但它解决的是软件工程里一个非常基础、非常容易翻车的问题如何让数据库的变更和代码的变更保持同步。核心要点V 脚本版本化迁移有去无回递增不可改R 脚本可重复执行适合视图和存储过程flyway_schema_history 表Flyway 的记忆别手动动它baseline-on-migrate已有项目的救星checksum 不匹配就报错这是保护机制不是 bug已合并的脚本不要改永远向前不回头如果你正在维护一个 SpringBoot 项目团队超过两个人数据库还没用 Flyway——强烈建议现在就加上。几分钟的配置换来的是未来无数次SQL 脚本漏执行事故的避免。干技术的都知道能被工具自动解决的问题就不要靠人的记性去保证。Flyway 就是这样一个工具。