
说句实在话“数据库 CI/CD”是那种听起来谁都会、做起来最容易翻车的领域。到 2026 年已经很少见到完全手动跑 DDL 的团队了大家至少知道要在流水线里加一步 migration 或 deploy 的命令。可工具真不能随便选处理不当上一个变更执行成功却没写记录导致下一个分支跑到生产直接栽在历史表结构上或者一条ALTER TABLE在十万行和十亿行的表上表现完全不是一回事。所以我想把这四款今年讨论最多的工具——Flyway、Liquibase、Atlas、Skeema——按实际使用逻辑重新拉出来盘一遍。“盘点”不是罗列官网能力而是帮你判断哪一款能长期匹配你团队的发布节奏、库表规模和可用性要求。这篇文章会讲清楚四款工具各自的底层思路、接入 CI/CD 的实测方式以及老业务团队怎么不踩坑地切过去。1. 我为什么在 2026 年还要重新做工具盘点1.1 应用部署早就自动化了卡流程的永远是数据库变更先看一个很常见的团队状态应用在 GitLab CI 上跑完测试、构建镜像、滚动发布总共不超过十五分钟。但到了数据库变更这一步流程一下子退回十年前——有同事用 DBeaver 连上生产库开个事务窗口把一句ALTER TABLE发出去然后在群里喊一声“我改完了”。这种模式最可怕的不是操作不规范而是没有任何“可审计状态”。等第二个人基于旧的表结构写代码或者第三个环境的 schema 忘同步了问题才会集中爆发。2026 年了微服务可以拆、K8s 可以滚动但很多团队面对库表变更依然靠人肉。CI/CD 工具盘点的前提是先承认这条链路一直没真正打通。1.2 数据库变更和应用代码有三个本质不同很多人把数据库迁移理解成“在流水线里多执行一条命令”这是最大的误区。应用代码和数据库结构有三点本质差异决定了工具设计的复杂度。第一代码产物是可替换的数据库状态是持续累积的。一个镜像部署坏了重新拉上一个版本就行一个错误的DROP COLUMN执行完旧数据不会因为你回滚镜像就回来。第二代码回滚是“切换版本”数据库回滚通常不是撤销 DDL而是“再写一段新的变更去补偿”逻辑完全不同。第三应用代码可以写 Mock 和单测数据库迁移要真正跑在真实引擎上才能暴露问题测试成本天然更高。所以在选工具前先得确认团队是否接受“数据库变更也是有生命周期的代码”这个理念。工具只是理念的落地载体。1.3 看似有 CI、实际没有的几种典型迹象我判断一个团队是不是真正把数据库纳入了 CI/CD从来不问“你用没用 Flyway”而是直接看几个细节Git 仓库里有没有 migrations 目录还是每个人电脑里各自留一套 SQL流水线里是不是只跑create table if not exists一旦面对老表就全程靠人工历史环境从零搭建时能不能用一套脚本把 dev、staging、prod 的 schema 完整重建有没有人通过 Navicat 或 DataGrip 的“同步数据库”功能在多个环境之间直接点按钮拉平发布窗口等待的到底是不是迁移审核而不是应用发布本身。只要中了两条以上说明数据库还没有真正进 CI/CD。后面所有工具选型都建立在“先承认这个问题存在”的基础上。2. 四款热门工具解决这个问题的底层逻辑2.1 Flyway靠脚本顺序和版本记录跑迁移Flyway 是最容易被接受的一类工具因为它的心智模型很直接你把每个变更写成一个带版本号的 SQL 文件比如V1__create_users.sql、V2__add_email_unique.sql工具启动时会扫描目录里的文件按版本号排序再和执行历史比对只跑那些没跑过的变更。它的核心状态存放在一个历史表里MySQL 下默认叫flyway_schema_history。第一次执行flyway migrate会建这张表以后每一次成功执行的脚本都会写入一条记录包括版本号、描述、脚本 checksum、执行时间和耗时。下次再跑工具只需要对比“文件里的版本”和“历史表里的版本”就能算出差异。为什么 Flyway 适合大量团队因为它把你最熟悉的 SQL 作为唯一输入几乎不需要额外学习配置。你可以只用一行命令接入flyway -urljdbc:mysql://localhost:3306/app \ -userapp_user \ -passwordsecret \ -locationsfilesystem:database/migrations \ migrateV1 文件内容也很朴素-- migrations/V1__create_users.sql CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, email VARCHAR(255) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP );但这套简洁模型也有限制Flyway 只关心“脚本顺序正确”并不理解数据库当前长什么样。如果某个历史脚本因为生产 hotfix 被改动过checksum 对不上flyway validate就会失败。它的哲学是“宁可阻断发布也不能让历史脚本被静默篡改”。2.2 Liquibase像配置管理一样管理每个变更集Liquibase 是另一种典型的迁移工具但它比 Flyway 抽象得更重。它把每一次变更包装成一个changeSet用唯一的id author标识并统一登记在DATABASECHANGELOG表里。核心文件是 changelog支持 XML、YAML、JSON 和 SQL 四种格式。以 YAML 为例一个建表变更长这样databaseChangeLog: - changeSet: id: create-users author: zhang changes: - createTable: tableName: users columns: - column: name: id type: BIGINT autoIncrement: true constraints: primaryKey: true - column: name: email type: VARCHAR(255) constraints: nullable: falseLiquibase 最大的特点是不一定要求你手写 SQL它可以跨数据库生成方言。同样一份 changelog换一个数据库 URL工具会自动适配。这给多数据库团队省了很多事但代价是学习曲线明显变陡。它还有一些 Flyway 没有的编排能力。比如preConditions可以控制“只有满足条件才执行”databaseChangeLog: - preConditions: - runningAs: username: app_migrator - changeSet: id: 20260101-001 author: zhang changes: - sql: sql: ALTER TABLE users ADD COLUMN nickname VARCHAR(50)context则能把同一套 changelog 里的一部分变更限定在特定环境执行。比如context: dev的变更只用于生成测试数据生产环境直接跳过。这种条件执行能力在大型企业里很有用但对大多数人来说过于灵活的后果就是排错时得同时理解“变更内容”和“执行上下文”两层状态。2.3 Atlas数据库领域的 TerraformAtlas 的思路和前面两款完全不同。Flyway 和 Liquibase 是迁移式工具记录的是“从 A 状态到 B 状态的过程”Atlas 是期望状态式工具你只需要描述“最终 schema 应该长什么样”由引擎去对比现实数据库并生成执行计划。你可以把目标 schema 定义成一个 HCL 文件schema app { charset utf8mb4 } table users { schema schema.app column id { type bigint } column email { type varchar(255) } primary_key { columns [column.id] } }然后执行atlas schema apply \ --url mysql://root:passlocalhost:3306/app \ --to file://schema.hclAtlas 会先连接目标数据库读取当前真实结构再和 HCL 文件做 diff计算出需要执行的 DDL。这就是为什么很多人叫它“数据库的 Terraform”。它天然支持漂移检测、CI lint、申请计划后人工审批。但期望状态模式也有隐性成本。新团队上手会比较顺畅因为只需要维护一份模型文件可一旦生产库被人手工改过、或者历史债非常深工具算出来的 diff 可能非常激进甚至尝试重建整张表。所以 Atlas 的落地往往伴随着严格的“计划评审”和只读账号配置不能把它当成免维护的自动同步器。2.4 Skeema带着 Online DDL 基因的 MySQL 专用派Skeema 是这四个里面最“专”的它只服务 MySQL 和 MariaDB 生态。它采用声明式思路但和 Atlas 不同的地方在于仓库里保存的不是一份抽象模型而是每一张表的完整CREATE TABLE定义。比如一个表目录会长这样schema/ .skeema users.sql orders.sqlusers.sql内容就是一张标准建表语句CREATE TABLE users ( id bigint unsigned NOT NULL AUTO_INCREMENT, email varchar(255) NOT NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;Skeema 会把目录里的文件当作期望状态连接数据库后逐表比较。skeema diff输出差异skeema push执行变更。它最受 MySQL DBA 欢迎的地方在于对大表变更的谨慎态度。很多团队在使用时会给它配上 gh-ost 或 pt-online-schema-change 一类的 online DDL 工具让ALTER TABLE以异步、轻锁的方式执行而不是直接在生产库上长时间锁表。它默认对明显的破坏性操作比较保守你可以通过配置显式放开。这种“默认安全、显式危险”的设计在多人协作时很值钱。不过这也就意味着如果你的数据库不止 MySQL 一种Skeema 基本帮不上忙。选它之前要想清楚团队是不是长期押注 MySQL 技术栈。3. 同是数据库 CI/CD 工具核心差异其实集中在四件事3.1 迁移式 vs 期望状态式决定了日常怎么写脚本前面已经提到Flyway/Liquibase 和 Atlas/Skeema 的代表性差异其实是两种世界观的差异。迁移式工具要求你把“数据库怎么从旧状态走到新状态”的过程全部记录下来每一个中间步骤都要可回放。好处是生产环境的每一步都是可预测的V3 执行完必然到 V3 的状态。坏处是如果团队积累了很长时间的手工变更补历史基线时非常痛苦另外多个分支同时升版本容易出现版本号冲突或迁移依赖错乱。期望状态式工具只要求你维护最终模型。日常开发时不会为了加一个字段专门创建一条新迁移而是直接改表定义文件然后让工具对比生成增量 DDL。好处是代码评审时非常直观reviewer 看到的是完整的表结构而不是一堆理解上下文才能看懂的碎片化脚本。坏处是工具对现实库的假设不成立时生成的计划可能不符合预期需要额外的人工判断层。我的建议是新项目、绿地团队可以认真考虑期望状态式但如果你有几套跑了五六年的老库过程中还穿插了大量历史 DDL迁移式的可预测性反而更稳。3.2 变更粒度影响 Code Review 和冲突解决方式工具之间的差异还体现在变更粒度。Flyway 以文件为最小单元一个文件通常包含多个语句review 时看“这个版本做了哪些事”Liquibase 把变更拆到 changeSet 级别一个 changeset 通常只做一个原子操作Atlas 的粒度是整个 schema 文件任何结构性改动都是 schema 模型的变化Skeema 的粒度则细化到每一张表。粒度越粗review 人越省心但冲突概率也越高粒度越细合并时越灵活但对团队规范的要求也更高。在很多团队里我见过用 Flyway 开发的人为了保证 review 轻松把一个版本的日志、审计、索引全塞进一个文件结果脚本长达几百行出了问题根本定位不到是哪一句。这不是工具的问题是需要先把变更规范定下来。3.3 回滚与幂等DDL 世界里没有完美的撤销很多人选型时会问“这工具支不支持回滚”我的标准回答是要分清“事务回滚”和“业务回滚”。DDL 在 MySQL 里很多操作是隐式提交的一旦执行成功再用工具去“撤销”基本不可能恢复已删除的数据。Liquibase 可以写 rollback 标签但那是你手动定义的逆向 SQLFlyway 社区版没有真正的 undo 机制靠编写新的迁移修复Atlas 和 Skeema 则倾向于“改回期望状态再 push 一次”本质上是再生成一次反向 DDL。真正到了生产事故场景我更推荐“用新版迁移修复”而不是依赖工具的自动回滚。因为自动回滚假设你和上一次执行时拥有完全一致的数据状态这在并发写入频繁的线上几乎做不到。把数据库变更当作不可变事件日志来对待出了错就再写一条补偿事件是长期维护最稳的思路。3.4 数据库生态兼容性不同团队有完全不同答案选择工具时还要看数据库类型覆盖。Flyway 和 Liquibase 的覆盖面最广主流关系型数据库基本都在官方支持列表里Atlas 也支持不少常见数据库同时对 MySQL 兼容协议有不错的适配能力Skeema 则划定在 MySQL/MariaDB。另外有些走 MySQL 协议的数据库在接入 Flyway 或 Liquibase 时可能会出现功能识别不完全的情况。原因在于迁移工具执行前往往要做数据库类型探测自动决定使用哪套方言生成 sql官方没有明确认证的兼容库就可能探测失败。遇到这种情况不能只看官网说明一定要先在临时环境里完整跑一遍迁移确认历史表读写和 checksum 记录都正常再放行。四款工具的基础能力对比如下维度FlywayLiquibaseAtlasSkeema变更思想顺序脚本迁移changeSet 迁移期望状态比对表定义文件比对主要格式SQLXML/YAML/JSON/SQLHCL/SQLSQL数据库支持多数据库多数据库多数据库仅 MySQL/MariaDB回滚方式社区版无真正 undo手写 rollback改状态重新同步改文件重新 push破坏性 DDL 默认处理不加限制不加限制可配置 lint默认较保守online DDL 支持自己控制语句自己控制语句依赖执行策略可配合 gh-ost/pt-osc学习曲线低中高中中这张表别只看某一列要结合你自己的“日常变更频率”和“生产库规模”去判断。4. 把这四款工具接进 CI/CD 流水线的实测做法4.1 流水线的三个阶段校验、计划、执行我经手过的数据库发布流程无论底层用哪款工具都建议切成三个阶段而不是一把梭地在 CI 里直接执行迁移。第一阶段叫校验。这个阶段不碰生产库只检查语法、文件命名、版本号、checksum 是否被篡改、目标文件能不能在临时数据库上完整执行。第二阶段叫计划。在隔离环境或临时 schema 上执行一次完整变更生成一份“这次发布到底要改什么”的产物给 DBA 或团队负责人做 review。第三阶段才是执行而且执行环境必须受保护普通开发者不应该拥有生产库的直接写权限。很多人把工具接进流水线时只写了最后一步导致 CI 看起来自动化了实际上生产库还是挂着高强度账号裸奔。4.2 Flyway 接入 GitLab CI 的参考配置以一个标准 GitLab CI 为例我习惯用 Flyway 官方镜像分 verify 和 deploy 两个 Stage。stages: - verify - deploy verify-db-migration: stage: verify image: flyway/flyway:latest services: - mysql:8.0 variables: MYSQL_DATABASE: app MYSQL_ROOT_PASSWORD: test script: - flyway -urljdbc:mysql://mysql:3306/app -userroot -passwordtest -locationsfilesystem:database/migrations -validateMigrationNamingtrue migrate注意这里的migrate不是直接连生产而是连一个临时 MySQL 服务。执行成功说明本次迁移文件可以在干净库上完整跑通。如果某条迁移依赖了上一个版本写入的数据也能在这个阶段暴露出来。生产环境的执行建议单独放到受保护的 Stage 里由发布人手动确认而不是合并分支后立刻自动执行。如果你使用 GitLab 的 protected environment可以配置仅 main 分支、仅维护者能触发。这样既保证自动化又留了人工闸口。deploy-db-production: stage: deploy image: flyway/flyway:latest environment: name: production only: - main when: manual script: - flyway -urljdbc:mysql://prod-host:3306/app -user${PROD_DB_USER} -password${PROD_DB_PASSWORD} -locationsfilesystem:database/migrations migrate-validateMigrationNamingtrue是我强烈建议加的它能让文件命名不规范、版本重复这些问题在早期就暴露。4.3 Liquibase 的 contexts 和 preConditions 在流水线中的应用Liquibase 接入流水线时要重点理解 contexts 和 preConditions。比如仓库里有一批 changeSet 只用于生成本地演示数据你可以给它们标记context: demo然后在 dev 环境执行时带上这个 context在 staging 和 prod 不执行。它不改变“变更脚本是否已经执行过”这个事实只是控制执行范围。liquibase \ --urljdbc:mysql://staging-host:3306/app \ --usernameapp_user \ --passwordsecret \ --changeLogFiledatabase/changelog.yml \ --contexts!demo \ update!demo表示排除 demo 上下文。我相信很多人第一次用 Liquibase 时会被这种“反向过滤”搞晕但它确实能让你一套 changelog 管理多个环境场景。preConditions 则更适合做“安全断言”。比如某条变更只应该针对 2026 年后的表结构生效如果生产库实际结构不符合预期可以直接让变更失败而不是盲目执行。4.4 Atlas 和 Skeema 在 CI 里的两个典型注意点Atlas 因为自带 plan 和 lint 思路接入 CI 时有天然优势。我常用的是先在 CI 里生成 plan然后让计划产物成为流水线的一个 artifact人工 review。核心命令大致类似这样atlas schema lint \ --dev-url docker://mysql/8/dev \ --url mysql://ci-user:passlocalhost:3306/app \ --exclude atlas_schema_replicalint 阶段可以自动拦截一些高危操作比如删除列、修改主键类型等。团队可以把这些规则打开让日常开发在提交代码阶段就收到反馈而不是等 DBA 人工审核时才揪出来。Skeema 接入 CI 的常见问题是 exit code 语义。skeema diff在有差异时返回非 0这和很多工具“有差异但成功”的语义不一样。第一次接入的团队常常会困惑为什么 CI 红了数据库好像也没坏。其实这正是你想要的它说明本地声明文件和生产库已经不一致必须有人去处理而不是让流水线继续往下走。我见过有人为了“让 CI 变绿”把 diff 的 exit code 强制忽略结果生产库漂移越来越远这是非常危险的做法。4.5 在 CI 里执行数据库迁移最容易踩的五个坑第一个坑多个 CI Job 并行跑迁移同写一个库。应用部署可以横向扩容数据库迁移不行。发布数据库变更的流程必须做成队列或闸口单写者执行。第二个坑把生产库密码存在 CI 变量里然后让所有开发者的分支都有权限触发。正确做法是生产发布单独环境、生产账号只授权给迁移用甚至通过堡垒机动态下发短期凭证。第三个坑忘了检查乱序迁移。Flyway 默认不允许 outOfOrder除非显式打开。有时候一个 hotfix 分支先合入生产再合回主分支就可能导致版本乱序。出现这种情况要理解工具为什么拒绝执行而不是暴力清历史表。第四个坑大表 DDL 没有 online DDL 策略。CI 里的迁移在五六分钟超时后失败可能不是因为语句写错而是表行数已经大到ALTER TABLE在执行期间锁表太久连接超时。生产的大表变更要提前想好是用 gh-ost、pt-osc还是直接改期望状态方案配合平台在线变更。第五个坑DDL 执行成功但历史记录写入失败。Flyway 在部分数据库上不是把 DDL 和 history 表写入放在同一个事务里如果网络闪断可能出现“表已经改了但迁移记录没写进去”的情况。这种状态下重试可能报对象已存在乱清记录只会更糟。正确做法是先人工核对当前 schema 实际状态再决定把历史记录补上、还是把变更脚本调整成幂等的形式。这是每个数据库发布负责人迟早会遇到的场景提前和团队约定好处理流程非常重要。5. 老业务团队切换到这套流程具体怎么走比较稳5.1 老库如何接一个“干净”的基线新项目从头接 Flyway 或 Liquibase 很简单但存量老库才是大多数团队的真实处境。生产库里已经有一堆对象是这些年人工执行留下的历史你不能让迁移工具从零开始再跑一遍否则一定会遇到“表已存在”的失败。以 Flyway 为例正确的基线步骤是先把当前生产库的真实 schema 记录为一个基线版本。假设你已经有一个V1__init_schema.sql对应着当前状态那么执行flyway \ -urljdbc:mysql://prod-host:3306/app \ -userapp_user \ -passwordsecret \ -locationsfilesystem:database/migrations \ -baselineVersion1 \ -baselineDescriptionbaseline from existing prod \ baseline这个操作会在历史表里插入一条版本1的记录但不会真的执行V1__init_schema.sql。之后 Flyway 从V2开始执行生产库已经存在的对象不会被重复创建。接完基线后还有一个必须做的验证在临时库里从零执行所有迁移文件确认可以重建出与生产一致的 schema。这个验证很多人会偷懒跳过但它是“新环境能不能可复现”的唯一保障。如果临时库重建后和真实生产库差异很大说明基线并没有覆盖全部对象需要补。Liquibase 的逻辑类似可以用它的 diff 能力生成一个 changelog 标记当前状态然后通过changelogSync让数据库认为自己已经执行到当前版本从下一个变更开始纳入管理。5.2 多分支并发开发时迁移文件怎么合流小团队一个人维护 migration 目录没问题但五六个人同时开发每个人都可能新建V5__xxx.sql合并时谁先谁后全靠人工非常容易出现依赖错乱。在 Flyway 语境里比较实用的约定是每个 PR 只允许递增一个版本号代码评审时看到重复版本直接拒绝合入合入顺序严格以 Git 提交顺序为准生产发布前在 CI 全量跑一遍迁移确保最新主干可以从零重建。另一个技巧是利用 ephemeral environment。环境规格可以砍但数据库迁移必须每次都从零执行一遍才能发现某个新脚本依赖了另一个还没合入的分支里的表。这种做法不是浪费资源而是把问题拦在 merge 之前。5.3 迁移脚本不进代码评审会有什么后果我见过一个团队他们把“数据库能连上”当成成功标准迁移脚本完全绕过代码评审DBA 只在生产出事时才介入。结果有一次开发为了删一个废弃字段直接把prod当测试库执行了ALTER TABLE orders DROP COLUMN legacy_note。表结构是改了但下游报表里还引用着这个字段整整一个下午业务报错。让迁移脚本进评审不是走流程而是在人脑层面确认三件事这条变更对存量数据的影响是什么下游代码有没有同步更新如果执行失败补偿方案是什么。Flyway 和 Liquibase 的版本文件、Liquibase 的 changeset 都是天然适合拿来 review 的产物Skeema 和 Atlas 的 diff 计划同样可以作为 CI artifact 供人查看。关键在于不要把“工具能跑通”当成“变更可以上生产”的全部。6. 我的选型参考什么样的团队优先选哪一款6.1 选型前先问自己四个问题第一个问题团队会一直围绕 MySQL 技术栈吗如果回答是“未来多数据库可能性很大”Skeema 就要先排除Flyway 或 Liquibase 更稳。第二个问题你希望团队维护“迁移过程”还是维护“最终模型”希望开发者只管写 SQLFlyway 上手最快希望像管理 IaC 一样管理 schemaAtlas 会更合口味。第三个问题生产库是不是已经有大表大表多、变更频繁Skeema 的 online DDL 协同能力值得倾斜考虑。第四个问题团队有没有专职 DBA 或者严格的变更审批角色没有专职 DBA就更需要 Atlas/Skeema 这类能生成直观 diff 并且支持高危操作拦截的工具。6.2 一张速查表快速定位团队实际情况我更推荐理由中小团队、全栈为主、希望只写 SQL 且快速落地Flyway心智模型最简单几乎不改变已知工作方式大型规范团队、多数据库并存、需要条件执行和复杂编排LiquibasechangeSet 和 context 等机制更成熟多格式支持以 GitOps 方式管理基础设施、希望 schema 像代码一样可评审Atlas期望状态模型天然适配 IaClint 能力强MySQL 专项团队、生产库表量大、非常关注 online DDLSkeema与表定义文件的管理方式贴合破坏性操作更可控表格只是一个起点真正的决定因素是你对自己团队的“变更频率”和“可容忍停摆时间”的判断。6.3 如果团队完全没接触过数据库 CI/CD第一步先做什么我的建议永远是别一上来就铺四款工具对比选型。第一步只做一件事把当前的迁移脚本放进 Git 仓库哪怕是手写的一堆*.sql文件先让它们有版本、有历史、有评审。然后选一个最顺手的工具Flyway 通常是最低摩擦的选择在 staging 环境完整跑通从零构建。等团队适应了“数据库变更也是发布的一部分”这个流程再去考虑要不要切换到声明式、要不要接入更强的高危操作 lint。工具选型不是一劳永逸的决策。以后如果业务从单一 MySQL 演进到多数据库或者从手工运维演进到平台化管理工作流迁移是可以分阶段做的。但前提是每一步都有完整的变更记录和可回放能力否则无论换哪款工具都只是在给历史债换一个包装。最后说一点个人体会我在实际维护数据库发布链路时最看重的其实不是哪款工具生成 SQL 的能力强而是它能不能帮你“拦住笨蛋操作”。所谓的高危 lint、默认安全策略、计划评审本质上都是用流程成本换生产稳定性。先把流程搭对再挑工具不迟流程还是靠人肉工具再热门也很难救你。