炸过一次生产库后,我强制全员上了Flyway

我曾天真地以为,只要代码用Git管好了,数据库脚本随便在群里发个 update.sql 文件,大家同步执行一下就行。

直到2018年那个黑色的周五晚上。

为了赶一个紧急的营销活动上线,后端老张手动连上生产库,把准备好的5个SQL脚本跑了一遍。结果,活动一开启,全站500报错。排查了整整两小时才发现:老张漏跑了第3个脚本,而第5个脚本因为字段不存在并没有执行成功,但他当时太困,根本没看控制台的报错日志。

那天之后,我就立了一条铁律:谁再敢手动连生产库跑DDL(数据定义语言),直接走人。

如果你现在的团队还在用“文档记录变更”、“群里发SQL”、“口头通知运维”这种方式管理数据库,请停下来读读这篇文章。这是我用真金白银的事故换来的血泪复盘。

一、 代码在裸奔,数据库在穿开裆裤

很多中小团队的技术负责人都有个错觉:DevOps = CI/CD流水线。

于是大家费劲巴拉把Jenkins搭建起来,代码自动部署了,结果数据库变更还是处于石器时代。这种割裂会导致一个极度尴尬的场景:

代码已经部署了新版本(依赖新字段 user_status),但数据库脚本还没跑。应用启动瞬间,JDBC直接抛出异常,服务起不来。

这就是典型的应用版本与数据库版本不一致

配图

在没有引入版本控制工具前,我们团队面临的真实痛点是:

  1. 环境差异大:开发库跑过了,测试库忘了跑,测试提Bug说功能不好使,开发查半天发现是少个字段。
  2. 脚本顺序乱:新来的小李在做功能A,老王在做功能B,两人都改了 orders 表。谁的脚本先跑?谁的脚本会覆盖谁?全靠吼。
  3. 无法回溯:生产库出问题了,没人知道现在的数据库结构到底是哪个版本的,更不知道上一次变更是谁在什么时候执行的。

数据库也是代码,必须进版本控制。 这是解决所有问题的根源。

二、 选型复盘:为什么我劝中小团队首选Flyway

市面上主流的就两款:LiquibaseFlyway

刚开始调研时,我被Liquibase的强大功能吸引了——支持XML/YAML/JSON定义,号称“写一次代码,到处运行”,还能自动生成回滚脚本。

但我带着团队试用了两周,差点引发“起义”。

真实劝退现场:

  • 学习成本高:让习惯写 ALTER TABLE 的老后端去写繁琐的XML标签,这简直是反人类。
  • 不可控:有时候XML生成的SQL并不是最优的,DBA(当时是兼职的)看了直摇头。
  • 过度设计:对于99%的中小团队,我们这辈子大概率只会用MySQL或PostgreSQL,根本不需要“跨数据库移植”的能力。

后来我们切换到了 Flyway。真香,全员无痛上手。

Flyway的逻辑简单粗暴:

  1. 你只需要写纯SQL文件。
  2. 按照约定命名,比如 V1__init.sql, V2__add_column.sql
  3. Flyway启动时,会自动在数据库里建一张表 flyway_schema_history,记录哪些脚本跑过了。
  4. 没跑过的脚本,它按顺序自动执行;跑过的,跳过。

我的建议:如果你的团队没有专职DBA,且后端开发人员对SQL比较熟悉,无脑选Flyway。不要为了那是1%可能用不到的“数据库无关性”去牺牲99%的开发体验。

三、 落地实战:别让工具变成新的负担

工具选好了,怎么落地才是关键。我见过很多团队引入Flyway后,反而搞得开发怨声载道。

我在团队里推行了这套**“三步走”**策略,目前已经稳定运行了3年。

1. 严格的命名规范(死刑线)

Flyway 默认依靠文件名排序。如果文件名乱起,执行顺序就乱了。 我强制要求命名格式为:V{版本号}__{描述}.sql(注意是两个下划线)。

  • ❌ 错误:update_user.sql (Flyway不认)
  • ❌ 错误:V1_update.sql (单下划线,报错)
  • ✅ 正确:V2023.10.27.01__add_user_status.sql

小技巧:我们后来放弃了 V1, V2 这种简单的数字,改用日期+序号。因为多人开发时,大家都抢着占 V3,合并代码时冲突冲突到想死。用日期大大降低了“撞号”概率。

2. 集成到SpringBoot启动流程

对于中小团队,我极其不推荐在Jenkins流水线里单独挂一个步骤去跑Flyway。

为什么?因为如果不小心配错了环境,你可能把测试环境的脚本跑到了生产环境。

我最推荐的做法是:随应用启动自动迁移。 在 application.yml 里简单配置一下:

spring:
  flyway:
    enabled: true
    baseline-on-migrate: true # 如果是旧项目改造,这个必须开
    locations: classpath:db/migration

这样,当应用启动时,Flyway会先抢占一把分布式锁,检查数据库状态,把缺的脚本跑完,然后再启动Spring容器。这保证了代码逻辑执行时,数据库结构一定是最新的。

3. 禁止修改已提交的脚本(Checksum陷阱)

这是新手最容易踩的坑。 小王提交了 V5__update.sql,测试环境跑完了。突然发现少加了个索引,他直接修改V5 文件的内容又提交了一次。

结果其他同事一拉代码,启动直接报错:Checksum mismatch

原因:Flyway会计算每个跑过的脚本的哈希值(Checksum)存到数据库。如果你改了文件内容,哈希值变了,Flyway为了保护数据一致性,会直接抛异常阻止启动。

解决方案

一旦脚本被合并到主分支,它就是只读的“化石”。 如果写错了,请创建一个新的 V6__fix_v5_error.sql 去修正,绝对不要回头改历史。

四、 避坑指南:DevOps路上的绊脚石

在推进这套方案的头半年,我们还是遇到了一些痛点:

痛点1:大表变更导致启动超时 有次我们在生产环境给一张2000万行数据的表加字段。Flyway随着应用启动执行,结果数据库锁表了5分钟。Kubernetes健康检查以为应用挂了,疯狂重启Pod,导致数据库死锁更严重。

修正方案: 对于涉及大表(超过500万行)的DDL,不要放进Flyway自动执行。 我们定了个规矩:大表变更走“DBA通道”,由运维在低峰期利用 gh-ost 或手动执行,并在 flyway_schema_history 表里手动插入一条记录“欺骗”Flyway说这个脚本跑过了,或者利用Flyway的 skip 机制。

痛点2:多分支开发的噩梦 开发A在 feature/a 分支加了 V10,开发B在 feature/b 分支加了 V11。 A先上线了,生产库有了 V10。 B后上线,Flyway发现生产库已经有 V10 了,现在来了个 V11,没问题。

但如果是B先上线(有 V11),A后上线(带 V10)。Flyway默认会报错,因为它不允许“插队”。

修正方案: 开启 outOfOrder: true 配置。允许不按顺序执行迁移。这对多分支并行开发的中小团队来说,是救命稻草

结语

配图

自从上了Flyway,我每周五下午发布时的心率从120降到了80。

配图

数据库变更不再是一个需要“开会确认”的高危动作,而变成了代码提交记录里一行普通的 Commit。每当看到 CI/CD 流水线全绿通过,我知道,那个曾经因为漏跑脚本而全员通宵修复数据的日子,再也不会回来了。

最后,送给各位3个马上能落地的行动步骤:

  1. 本周内,在一个非核心的小项目中引入 Flyway/Liquibase,先跑通流程。
  2. 定规矩:从今天起,所有DDL语句禁止在聊天工具传输,必须落成SQL文件。
  3. 检查现有项目:如果是遗留老系统,使用 Flyway 的 baseline 功能,以当前数据库状态为基准,从此开始版本化。

你在数据库变更管理中遇到过最离谱的“坑”是什么?是删库跑路还是字段消失?欢迎在评论区分享你的血泪史。