数据库CI/CD选型与实战:对比Flyway、Liquibase、Atlas和Dolt
2026/9/5 14:10:43 网站建设 项目流程

应用代码的持续集成、持续交付做了那么多年,为什么一提到“发布上线”,大家还是会下意识紧张?因为真正容易卡住的地方往往不在业务代码,而在数据库:表结构怎么改、历史数据怎么迁、多个环境的 schema 怎么保持一致。数据库 CI/CD 要解决的就是这件事——用对待代码的方式去管理数据库变更,让每次结构变更都能经过评审、自动化测试和可控执行,而不是靠人肉登服务器敲 SQL。2026 年再看这个领域,Flyway、Liquibase、Atlas、Dolt 是绕不开的四款热门工具,这篇文章就把我的选型思路和实操记录摊开来讲,给被数据库上线流程折磨过的后端、DevOps 和数据同学做个参考。

1. 为什么数据库 CI/CD 在 2026 年依然是重点话题

1.1 应用发布已经自动化,数据库变更却还在“手工时代”

见过太多团队的消息流变成这种形状:开发提交代码,GitLab CI 自动跑单测、构建镜像、滚动发布,一气呵成;但数据库结构变更还是由某个人把 SQL 语句贴到生产环境终端里执行。运气好的时候,SQL 语句提前经过 review;运气不好的时候,改错了字段类型,线上直接报错,然后一群人开始排查“这个表怎么多了一列”。

这里的问题不是某个人不够细心,而是流程错了。应用部署可以随时回滚到上一个版本,因为代码有版本管理;数据库却不一样,数据库一旦被改过一次,很难恢复成“上个结构版本”的样子。手工执行更是让所有变更都依赖个人记忆,三天后没人说得清线上库跟测试库差在哪里。

数据库 CI/CD 工具就是来戳破这个局面的。它把“数据库该长成什么样”以及“每一步怎么变成这样”都沉淀成可提交、可评审、可回放的文件。CI 阶段可以在临时库上跑一遍验证,CD 阶段再把变更正式落到目标库,每一步都有记录、有历史、可追踪。到了 2026 年,这早已不是锦上添花,而是稍微有点规模的项目都该具备的基础能力。

1.2 三类技术路线,对应完全不同的管理思维

市面上的数据库 CI/CD 工具在底层设计上其实分三条路线,理解清楚路线,后面选工具才不会被官网宣传带偏。

第一类是版本化迁移(Migration),代表是 Flyway、Liquibase。核心思路是:你按顺序维护一串变更脚本,比如 V1 建表,V2 加字段,V3 加索引,工具负责记录哪些脚本已经在目标库执行过,没有执行过的再按顺序补上。这个模型的好处是每一步都有清晰的“前后”关系,和执行记录对得上,适合绝大多数团队的直觉。

第二类是声明式状态对比(Declarative Schema),代表是 Atlas。它不关心你分了多少步,它只关心你定义的目标状态:这张表应该有哪几列,唯一索引应该建在哪几个字段上。工具自己去连数据库看当前状态,然后算出两者之间的差异,生成一个变更计划,你再决定是否执行。这种方式非常适合数据库环境已经漂移,或者你不想维护几百个编号脚本的场景。

第三类是把数据库本身做成 Git 版本库,代表是 Dolt。Dolt 不是挂在数据库外面的迁移工具,它能像 Git 一样对数据库做分支、提交、合并,数据和结构都纳入版本管理。在这种模型下,数据库变更几乎就是“写代码”——拉分支、改表、提交、合并、推送上线,流程体验非常原生化。

我个人的感觉:没有哪一条路线绝对高级,关键看你团队对数据库变更的可控性要求,以及现有基础设施能接受哪种工作方式。

2. 四款热门工具逐项拆解:参数、定位与适用边界

2.1 Flyway:轻量直接的老牌迁移工具

Flyway 在数据库 CI/CD 这一话题里的地位,基本等于“数据库界的 Git”。它把自己的核心逻辑做到了足够简单:你的代码里有一个/migration 目录,里面放着 V1__init.sql、V2__add_column.sql 这类命名规范的脚本,执行 flyway migrate 时,Flyway 会扫描本地脚本、连接数据库、查询记录表 flyway_schema_history,把所有还没执行过的脚本按版本号顺序执行。

我为团队选型时最看重的一点就是:Flyway 的学习成本可以做到几乎为零。后端同学只要懂 SQL,半小时就能把本地流程跑通。它支持的数据库也很广,PostgreSQL、MySQL、Oracle、SQL Server 都在覆盖范围内,而且官方文档里对 CI/CD 管道有明确推荐做法,很容易嵌入 GitHub Actions 或 GitLab CI。

Flyway 的短板在于可回滚性比较弱。它默认只关心“向前执行”,如果你写了一个带破坏性的变更,执行完才发现要回退,通常要靠你自己写反向 SQL,而 Flyway 不会主动帮你管理 rollback。另外这类严格依赖脚本编号的工具,一旦多人并行修改同一批脚本,非常容易在日常开发中产生 checksum 校验不一致的冲突。

2.2 Liquibase:流程更重,但是企业级变更控制更完整

Liquibase 和 Flyway 表面上是同类工具,但用起来完全是两种感觉。Liquibase 的迁移文件叫 changelog,它允许你用 SQL、XML、YAML、JSON 四种格式描述一次变更。比如用 YAML 定义“给 products 表新增 sku 字段”,Liquibase 会把这个 operation 转化成对应数据库的 DDL,具备很强的跨数据库移植能力。

Liquibase 在流程控制上更复杂也更完整:它可以给每个 changeSet 配置 precondition,只有满足条件时才执行;可以给脚本打 context 和 label 标签,把不同环境的变更区分开;还提供了 command 级别的 rollback,配合自定义回滚 SQL,让团队在出问题时有更清晰的撤退路线。这些能力在金融机构、国企项目或强管控场景里,是比较核心的加分项。

代价就是概念多、配置重。新成员要理解 changeSet 的 id+author 唯一性、databasechangeloglock 锁表机制、diffChangelog 生成规则,通常需要两三天上手。如果你的团队只是想让数据库变更跑进流水线,Liquibase 可能有点“过度设计”。但如果你需要严格的审批和审计记录,不要犹豫,直接选它。

2.3 Atlas:以目标状态驱动,真正适合未来 schema 自动化

如果 Flyway 属于“盯着每一步看怎么做”,Atlas 就是“只盯着目的地看怎么到”。它允许你用 HCL 或普通 SQL DDL 定义数据库期望的目标结构,示例大概长这样:

table "products" { schema = schema.public column "id" { type = "int" null = false } column "name" { type = "varchar(255)" } primary_key { columns = [column.id] } }

实际运行时,Atlas 先连一个 dev 环境或临时库,算出“当前库结构”和“目标结构”的差异,把 ADD COLUMN、CREATE INDEX、DROP COLUMN 这些操作整理成一个可审查的变更计划。比较温和的操作会自动执行,危险的破坏性操作则可以被策略拦截,也可以选择让相关人员在 CI 流程里 Review 后批准执行。

Atlas 这种模型面对的是 2026 年更常见的痛点:环境数量越来越多,schema drift(结构漂移)越来越严重,根本没人维护得清几十个环境各自执行到哪个迁移版本。用目标状态声明,工具自动做“收敛”,比手工维护一套几百行的迁移历史更省心。它非常适合已经具备代码评审文化和较强自动化能力的团队,但对刚接触的新手来说,要理解 dev-url、lint policy、migration directory 这些概念,需要一定学习成本。

2.4 Dolt:把 Git 搬进数据库的激进派

Dolt 是一个非常特别的选手。它不是“数据库迁移工具”,它本身就是一个 MySQL 协议兼容的数据库。也就是说,你可以用现成的 MySQL 客户端去连它,但它支持 git add、git commit、git branch、git merge。在 Dolt 里,表结构的每次修改和每一行数据的变动,都会被当作一次提交记录下来,完全按照 Git 对象模型存储。

这对数据库 CI/CD 的影响是革命性的:开发可以像处理代码一样为数据库拉分支。比如我想在测试库上加一个唯一约束,我先建一个分支 add_unique_sku,在分支里执行 ALTER TABLE,提交并推送到远端,然后像开 pull request 一样发起合并。如果与主分支冲突,Dolt 会明确提示冲突发生在哪张表的哪些行,相比传统迁移脚本的报错要直观得多。

当然,Dolt 也有它的问题。一是它必须作为新的数据库实例来运行,对于“已经在生产环境跑了好几年 MySQL”的团队来说,短期内不可能直接把业务流量切换到 Dolt 上,更多只能把 Dolt 用于开发环境、测试环境,或者作为 CI 流程中的临时数据库。二是它的生态还在发展中,第三方工具、监控体系、云数据库托管支持都没有传统 MySQL 那么成熟。如果你追求极致一致性和数据可回放能力,Dolt 值得在 2026 年保持关注,但正式引入前一定要做足技术验证。

3. 用同一个需求走一遍:四款工具的落地姿势与关键细节

3.1 需求定义:给老表新增“唯一约束”字段

多讲空洞的概念容易飘,我拿一个特别常见的需求来对比四款工具的实际操作。

假设业务系统里已经有一张原始的商品表 products,早期建表时没有设置业务编号字段。现在产品要求增加一个商品唯一编号 sku,并且这个字段不能重复。转换到数据库层面就两步:给 products 表新增一个 sku 字段,然后再给 sku 字段加上唯一索引。

先看一下基础表的 SQL:

CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL );

然后我们期望最终结构是:

CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, sku VARCHAR(64), CONSTRAINT uk_products_sku UNIQUE (sku) );

下面我会按 Flyway、Liquibase、Atlas、Dolt 四种方式,分别说明变更文件怎么写,以及落地到 CI/CD 管道时需要注意什么。

3.2 Flyway:写一个 V2 脚本就能跑

用 Flyway 的话,我只在 migration 目录下加一个文件,命名 V2__add_sku_to_products.sql,内容就是最普通的 SQL:

ALTER TABLE products ADD COLUMN sku VARCHAR(64); ALTER TABLE products ADD CONSTRAINT uk_products_sku UNIQUE (sku);

在 CI 中执行时,命令可以长这样:

flyway -url="jdbc:mysql://localhost:3306/app_db" \ -user="root" \ -password="secret" \ -locations="filesystem:./migration" \ migrate

Flyway 完成后会往 flyway_schema_history 表插入一条记录,记录版本号、脚本描述、执行时间、脚本 checksum。下次再跑同一套迁移时,它会比对文件 checksum 与库里的记录,如果发现某个已执行脚本被改动过,就会直接报错拒绝执行。

我实际使用中的建议是:不要把 V2 脚本和另一个任务里的 SQL 塞进同一个迁移文件。Flyway 的一个版本就是一个原子基线,虽然部分数据库不支持跨语句事务,但你至少要在审查时把它当成一个完整变更单元。多人协作时,最好约定 V 后面的编号由主线分支统一分配,不要每个人在分支里都写 V1.1、V1.2,否则合并后很容易出现顺序冲突。

3.3 Liquibase:用 YAML 描述变更,并主动准备回滚

Liquibase 的典型 changelog 文件可以这样组织。我先写一个主文件 db/changelog/db.changelog-master.yaml:

databaseChangeLog: - include: file: db/changelog/changeset/add-sku.yaml

然后在 changeSet 里描述具体变更:

databaseChangeLog: - changeSet: id: add-sku author: zhang_san changes: - addColumn: tableName: products columns: - column: name: sku type: varchar(64) - addUniqueConstraint: tableName: products columnNames: sku constraintName: uk_products_sku rollback: - dropUniqueConstraint: tableName: products constraintName: uk_products_sku - dropColumn: tableName: products columnName: sku

执行时,Liquibase 会在数据库里维护两张表:DATABASECHANGELOG 记录每个已执行的 changeSet,DATABASECHANGELOGLOCK 用来防止多个节点同时跑 changelog 造成并发执行。CI 接入时用以下命令即可:

liquibase --changeLogFile=db/changelog/db.changelog-master.yaml \ --url="jdbc:mysql://localhost:3306/app_db" \ --username=root \ --password=secret \ update

这里有个很容易踩的坑:Liquibase 的 changeSet 以 id+author+filename 三个信息作为唯一标识。如果后来你改了 id,或者移动了文件路径,Liquibase 会认为这是一个全新的 changeSet,在目标库里再执行一遍。所以一旦某个 changeSet 被合并并执行过,尽量不要改动它的标识,也不要轻易修改 changes 内容。真要调整,就再写一个新的 changeSet 去“订正”,而不是去编辑历史。

3.4 Atlas:只维护最终 schema,让工具自己算差异

Atlas 的做法不一样,我不用为这次变更单独写一个迁移脚本。我只需要确保目标 schema 文件里已经是“最终状态”。用 SQL DDL 形式来表示的话,schema.sql 文件里是:

CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, sku VARCHAR(64), CONSTRAINT uk_products_sku UNIQUE (sku) );

连接目标库以后,执行 apply 命令:

atlas schema apply --url "mysql://root:secret@localhost:3306/app_db" \ --to "file://schema.sql"

Atlas 会先去读 app_db 的当前状态,发现它还是旧版结构(没有 sku 字段),于是自动生成一条 ALTER TABLE 语句,把数据库改到目标状态。为了更安全地操作,我通常不会直接一行 apply 上生产,而是先让 CI 生成 diff:

atlas schema diff --from "mysql://root:secret@localhost:3306/app_db" \ --to "file://schema.sql" \ --dev-url "docker://mysql/8/app_db"

生成出来的差异 SQL 可以出现在 CI 的 MR/PR 评论里,让有权限的人在界面点击“批准”,再由下一个 job 执行。这样既享受了声明式模型的好处,又保留了对生产变更的人工把关。

Atlas 在 2026 年的 CI/CD 生态里还有一个优点:它提供 schema lint 能力,可以在发现 DROP COLUMN、DROP TABLE 这类破坏性操作时直接拦截。对付那些只想改个索引却把整表删了的“乌龙变更”,这种策略非常管用。

3.5 Dolt:用 Git 分支与合并来管理变更

用 Dolt 进行这次变更,前提是应用已经使用 Dolt 作为数据库。它的流程是我个人觉得最接近“代码开发”的。我先在 Dolt 里创建一张 products 表,把它推送到远程,然后开始拉分支:

dolt checkout -b feature/add_sku dolt sql -q "ALTER TABLE products ADD COLUMN sku VARCHAR(64);" dolt sql -q "ALTER TABLE products ADD CONSTRAINT uk_products_sku UNIQUE (sku);" dolt add . dolt commit -m "add sku field with unique constraint"

提交完成后切回主分支,把特性分支合并进来:

dolt checkout main dolt merge feature/add_sku

如果 main 分支上已经有别的同事改了同一张表,Dolt 会像 Git 一样提示出现 merge conflict。你可以用 dolt conflicts cat 查看冲突行,选择保留哪一版或者手工合并。合并完成后再把 main 推送到远程数据库实例,整个发布链路就完成了。

从“数据库 CI/CD 工具”的角度来看,Dolt 最大的价值不是直接替代迁移工具,而是让“数据库也可以被代码评审”。DBA 不需要脑补一个 ALTER 在生产环境会有什么影响,直接看分支 diff 就能判断。这个体验在处理数据质量问题、修 bug 或做小型实验时尤其好用。

4. 选型对比与经验建议:不同团队请对号入座

4.1 核心参数横向对比表

为了更直观地展示四款工具的差异,我按自己在选型中最关心的维度整理了一张对比表:

维度FlywayLiquibaseAtlasDolt
核心模型版本化迁移版本化迁移 + 细粒度控制声明式状态对比Git 化数据库
变更描述方式SQL 脚本SQL/XML/YAML/JSONHCL 或 SQL DDLSQL 直接改库
rollback 能力需自写反向脚本支持声明更完整通过备份/计划回滚相当于 git revert
学习门槛中高
破坏性变更保护弱到中强(lint/policy)中(diff 可见)
适用数据库范围很广很广主流关系型MySQL 协议环境
生产环境大表 DDL需谨慎需谨慎需谨慎需评估
CI/CD 友好度中高

这里要单独解释一下“生产环境大表 DDL”这一行。四款工具本身都只是发出 ALTER TABLE 语句,真正执行快慢取决于 MySQL/PostgreSQL 对表锁、行锁、在线 DDL 的支持程度。针对超大数据量,我一般会建议在迁移流水线里配合 gh-ost、pt-online-schema-change 这类在线变更工具,或者让 DBA 人工介入拆分步骤,而不是盲目交给迁移工具一把梭。

4.2 按团队画像推荐的选型结论

如果你是小团队、项目刚开始、成员后端经验偏多,我建议从 Flyway 入手。原因很简单:它把数据库变更压缩成了“写 SQL + 编号”,零配置也能跑起来。团队很快就能养成“所有结构变更进代码仓库”的习惯,后面再往上加 CI 验证步骤都很顺。

如果你在金融、政企或者对流程审计要求很高的项目里,直接看 Liquibase。它的 changelog 体系、precondition、context、rollback 概念虽然繁琐,但恰好匹配这类项目“每一步都能说清来龙去脉”的合规诉求。面对客户审计时,DATABASECHANGELOG 和锁表记录就是现成的证据。

如果你的团队已经受够了 schema drift——每个环境结构对不上、线上改过什么没人知道,Atlas 是更加现代的选择。你不再需要逐环境去数脚本执行到哪一步,只需让 Atlas 把大家“拉回到”同一个目标状态。但要提前想清楚,Atlas 的迁移不涉及数据本身,应用初始化时需要的数据(比如字典表、配置表),仍然得单独准备数据迁移方案。

如果你们追求研发体验,愿意接受新技术栈,而且核心痛点是本地开发环境难搭、测试数据不可回放,那 Dolt 值得认真试水。它能把测试库、预发布库甚至 CI 临时库全用 Git 分支管理起来,开发随时 checkout 一个历史版本数据来复现 bug,这种体验是其他工具给不了的。

4.3 我的一套快速决策检查清

面对一个具体项目时,我一般只问自己几个问题:

第一,团队的数据库变更目前是“脚本维护”还是“结构漂移”占主导?如果是前者,Flyway 或 Liquibase 不费力;如果是后者,直接考虑 Atlas。

第二,数据库对象的管理是否需要严格审计和回滚制度?需要的场景选 Liquibase,不需要的选 Flyway 即可。

第三,除了结构,我们是否还想管理“基础数据”或者“测试数据”?想认真管理就研究 Dolt,否则就在迁移脚本里用 Insert 语句,或单独接一个数据初始化任务。

第四,生产环境是否有数十亿行的大表?如果有,任何工具都只是辅助,真正的核心是数据库本身的在线变更能力和变更时窗口的调度。

这四个检查点能帮你过滤掉大部分信息噪音,把选择收敛到一两个候选上。

5. 高频报错与多环境实施中的避坑心得

5.1 Flyway 和 Liquibase 最常见的“翻车现场”

Flyway 用户提得最多的问题是 checksum mismatch:某天 CI 突然红了,报本地脚本 checksum 与数据库记录不一致。原因基本只有一个——某个已执行过的脚本被别人修改过,哪怕只改了一个空格,Flyway 也能检测出来。这种情况下不要去执行 flyway repair 了事,而是要回到 Git 历史里看这个脚本为什么被改。如果确实是历史脚本写错了,正确做法是新增一个 V 脚本做补偿修正,而不是回头改旧脚本。

Liquibase 的经典问题则是 DATABASECHANGELOGLOCK 一直显示锁住。常见诱因是有人在本地手动执行了 liquibase,但命令半路被 Ctrl+C 终止,锁没有正常释放。解决办法不是直接删锁表,而是先通过 DATABASECHANGELOGLOCK 查一下锁的持有时间。如果确实是僵尸进程留下的,confirmed 之后再 DELETE 对应记录,否则可能把正在跑的发布任务打断。我在多个项目里见到过因为删锁导致两个发布 job 并发执行同一个 changeSet,最后数据被重复变更的情况,所以处理锁表一定要先确认进程状态。

5.2 Atlas 与 Dolt 各自容易忽略的坑

Atlas 的声明式模型看起来很智能,但它有个天然风险:工具只看到“当前结构”和“目标结构”,却不理解你真正想做什么。比如某天 schema 文件里漏写了某个字段,Atlas 就可能在下一次 apply 时生成一条 DROP COLUMN 指令。若不设置 lint policy,哪怕只是文件误删一行,也可能导致生产数据列被删除。所以我坚持所有 Atlas 变更都必须经过 diff 评审,并且开启 destructive 操作拦截策略。

Dolt 的坑更多集中在转换成本。很多团队想先用 Dolt 做测试库,再让测试数据同步回原有 MySQL,结果发现两个数据库的复制机制、二进制日志格式、SQL 语法边界都存在差异。Dolt 是一个独立数据库,不是一个“MySQL 同步插件”。如果你只因为新鲜感就把它接入现有主从架构,会在同步环节耗费大量精力。Dolt 更适合你确定要用它当主力数据库的场景,或者只在纯 Dolt 环境中完整走开发、测试、发布流程。

5.3 大表 DDL 与 CI 重试策略

最后提醒一个比工具选型更底层的问题:数据库 CI/CD 跑得再顺,也不能让一个业务高峰期的 ALTER TABLE 把库锁死。常见的迁移窗口策略是:先确认表数据量级,再选择数据库原生在线 DDL 能力,必要时拆成“先加可空字段、再回填数据、再加约束”三步走。

CI 重试策略也容易让人翻车。一个迁移 job 如果因为网络抖动失败,平台自动重试时,Flyway 可以依靠记录跳过已经成功的部分,Liquibase 也会把已执行的 changeSet 跳过,但如果迁移脚本写得不是幂等操作,重试仍然有风险。更稳妥的方式是对“会产生外部影响的步骤”设置 no-retry,或者把“迁移执行”和“迁移验证”分成两个阶段,只在验证阶段做重试。

我在实际推进数据库 CI/CD 项目时,最深的感触是:工具能解决流程自动化的问题,但不能帮你补团队规范和数据库基础知识的课。先让大家在分支里写 SQL、在 MR 里 review 变更、用临时数据库验证无损性,再引入再复杂的工具都会顺手很多。如果你所在团队还没开始做这一步,不妨就把 Flyway 当一个起点跑起来——等大家习惯“数据库变更也要走代码评审”之后,你会发现后面无论是切 Liquibase 还是 Atlas,都会顺利得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询