资讯详情

资讯详情

从手写脚本到Schema as Code:用Atlas统一管理MySQL迁移

如果你也管过那种跑了两三年、迁移脚本已经堆到上百个的 MySQL 库你一定明白我说的场景谁能说得清003_init.sql和004_init_2.sql到底哪个跑过某个线上环境里是不是少了一条 ALTER TABLE为什么测试环境重建 Schema跑到一半报外键错误我自己就栽过好几回。去年年中有一次同事在本地改了一条历史迁移脚本没告诉我结果 staging 再打版本的时候整条迁移链路全花了。那天我对着 MySQL 的 information_schema 手工核对到凌晨一点才找到是哪个旧脚本被改过。这件事之后我认真做了选型最终把项目里的数据库 Schema 迁移切到了 Atlas。Atlas 是一个开源的“Schema as Code”数据库管理工具支持 MySQL、PostgreSQL、MariaDB、SQLite、SQL Server 等常见数据库。它既支持声明式工作流——你用 HCL/SQL/ORM 描述想要的 Schema工具负责算出差异并执行也支持版本化迁移——把每次变更变成可审查、带校验和的迁移文件。如果你是后端工程师、DBA 或者负责基础架构的同事又刚好被手写迁移脚本折磨过这篇是纯实操向的经验记录没有教科书式的废话。1. 项目背景为什么我放弃手写数据库迁移脚本改用 Atlas1.1 手写迁移脚本的三个典型坑先说最常见的三个坑我估计大部分团队都中过招。第一个是序号和时间戳混乱。团队一多大家都习惯在migrations/目录里放20240101_xxx.sql这种文件。问题是两个人同时在各自分支里加了变更合并时要么产生冲突要么顺序完全对不上。更麻烦的是某些同事喜欢改已经合并过的旧脚本改完也不会重新跑一遍全量流程生产库里新旧状态根本没法对齐。第二个是执行状态不可靠。我们当时是自己写了一个小工具记录哪些脚本跑过、哪些没跑过记录表放在数据库里。听着挺稳实际上只要有人手动连上生产库执行了一条 SQL记录表里就少了一条后续所有人的迁移都会踩雷。而且这个记录表本身不在迁移体系中属于“先有鸡还是先有蛋”的循环依赖。第三个是环境漂移。本地开发、测试环境、预发环境、生产环境四个地方的 Schema 永远不可能完全一致。因为总有人“临时”手动加过字段现场改完又没同步到脚本。时间一长你根本不知道哪个环境是真“标准”的。等你上线新功能时才发现新加的索引在原库上早就存在迁移脚本一执行直接报Duplicate key name。这些问题不是靠“更自律”就能解决的。人的可靠性永远比不过机器的确定性。我当时就想要一个工具能管理迁移文件、能校验执行状态、能在 CI 里拦住问题最好还能让我少写一半重复的 SQL。1.2 为什么选 Atlas声明式和版本化一次都要选型时其实先看了几款主流工具。Flyway 和 Liquibase 都算老牌功能也很成熟它们主导的是“版本化迁移”思路你写一串顺序执行的 SQL 脚本工具负责记录执行版本。另一类是以 Prisma Migrate 为代表的 ORM 自带迁移只对自己管理的表有效混合使用会很别扭。Atlas 让我眼前一亮的地方在于它不逼你在“声明式”和“版本化”之间二选一。它把两种工作流统一到了一套引擎里你可以先用 HCL 或者 SQL 描述最终想要的 Schema 状态然后让 Atlas 去对比当前数据库状态自动生成精确的迁移语句。生成的这些语句会落成版本化迁移文件之后用atlas migrate apply执行。换句话说它既能“告诉工具你要什么”也能“让工具把过程写成档案”。这一点非常贴我的实际需求我既想要声明式地描述理想的表结构少写 DDL又希望存在可审查、可回滚的迁移记录而不是在工具界面上点一下就完事。另外Atlas 底层对多种数据库方言做了抽象不是简单把 SQL 文本串起来。MySQL 和 PostgreSQL 在索引、默认值、检查约束上的差异它会生成各自对应的 DDL。这意味着如果项目以后从 MySQL 迁到 PostgreSQLSchema 定义可以作为公共资产复用不需要从头把迁移脚本翻译一遍。1.3 和 Flyway、Liquibase 的横向对比用一张表说清楚我当时的对比结论对比维度AtlasFlywayLiquibase核心模式声明式 版本化统一版本化脚本版本化变更集Schema 定义方式HCL / SQL / ORM 对象纯 SQLXML / YAML / SQL生成迁移文件支持自动 diff不支持手写为主支持但配置较重数据库方言抽象强跨库生成 DDL中等各库脚本独立中等校验机制自带的校验和 重建验证校验和checksum破坏性变更检查migrate lint支持较弱有预检查能力上手成本低到中一条命令即可 diff低中高如果项目只是简单 CRUD、固定几种数据库、团队也很小只用 Flyway 手写 SQL 问题不大。但如果你的数据库环境复杂、需要自动化生成迁移、还要在 CI 里做静态检查Atlas 的优势就会体现得很明显。2. Atlas 核心原理声明式与版本化迁移其实是同一套引擎2.1 声明式工作流要什么给什么diff 由工具算要理解 Atlas得先抓住一个关键概念desired state也就是你“想要的数据库最终状态”。在声明式工作流里你不再写ALTER TABLE而是直接描述“users 表应该长什么样”。Atlas 支持几种描述方式最常见的是 HCL 文件长得有点像 Terraform不过专为数据库设计。比如schema orders { charset utf8mb4 } table users { schema schema.orders column id { null false type bigint unsigned true auto_increment true } column email { null false type varchar(255) } column created_at { null false type datetime default sql(CURRENT_TIMESTAMP) } primary_key { columns [column.id] } index idx_users_email { unique true columns [column.email] } }这段 HCL 表达的是一个很常见的用户表结构。你可能会问这不还是得像写建表语句一样写一遍对但它和手写 DDL 的本质区别在于这份文件是唯一事实来源source of truth。你不需要再维护一堆V1__init.sql、V2__alter.sql、V3__add_index.sql只需要维护“最终长什么样”。真正发生动作的是atlas schema apply。它会把这张用户表定义和当前数据库里的真实结构做一次 diff然后生成并执行必要的 DDL。比如数据库里已经有一张users表但缺少email这一列它就会生成ALTER TABLE users ADD COLUMN email varchar(255) NOT NULL。你全程不需要手动写这一次变更。diff 的底层实现不是简单做文本比对而是把 Schema 解析成带类型的抽象语法树再把两边数据库的 Schema 结构都放到同一套内部模型里做深度对比。这样它就能智能识别“字段改名了”和“字段被删掉又新增了一个”之间的区别不会轻易把成本很高的重建操作误判成一次轻量修改。2.2 版本化工作流把算出来的差异落成文件很多人把 Atlas 单纯理解成“声明式迁移工具”但它在实际生产环境里更常用的反而是版本化迁移工作流。版本化迁移的核心是三步你用 HCL 或 SQL 文件描述目标 Schema运行atlas migrate diff工具比较“当前迁移目录里最后一个状态”和“目标 Schema”之间的差异工具自动生成带时间戳前缀、包含up.sql和down.sql的迁移文件记录在指定的迁移目录里。生成的迁移文件长这样migrations/ 20250301120000_init.up.sql 20250301120000_init.down.sql atlas.sum其中atlas.sum是整个迁移目录的校验和文件。别小看这个文件它把之前手写脚本时代“改了老文件没人知道”的问题彻底解决了。Atlas 会在执行迁移前校验当前目录的校验和是否与atlas.sum一致如果有人在本地改动了历史迁移文件后续所有环境都会报校验和不匹配你立刻就能发现问题。版本化迁移执行用的是atlas migrate apply。它会去目标数据库里的atlas_schema_revisions迁移记录表读取当前版本然后按顺序执行尚未应用的文件。每一个文件执行后都会记录下来所以即使崩溃了也不会重复执行同一段 SQL。2.3 dev-url 为什么是“刚需”用 Atlas 的时候你会发现很多命令都要求传--dev-url。不少新手嫌麻烦直接忽略结果命令报错。这个参数我一开始也不理解后来才明白它的作用。--dev-url指向一个临时的空数据库Atlas 会在这个临时库上回放迁移历史、模拟 Schema 的完整状态再基于这个模拟结果和 desired state 做 diff。这样有两个好处不会碰你真实的开发库或生产库所有探查操作都在隔离环境里完成能更准确地推断中间态尤其是有多个版本迁移连着发生时直接对比两个端点容易漏掉中间变更。实战中如果本机装了 Docker可以直接用一个干净的临时库atlas migrate diff \ --dir file://migrations \ --to file://schema.hcl \ --dev-url docker://mysql/8/orders这行命令会自动拉一个 MySQL 8 的临时实例跑完对比后销毁。没有 Docker 的话也可以指定本地空库atlas migrate diff \ --dir file://migrations \ --to file://schema.hcl \ --dev-url mysql://root:passlocalhost:3306/temp_dev但千万注意--dev-url指向的数据库必须是空的至少不能包含业务数据。因为它要做重放和清理把业务数据放进去等于拿生产开玩笑。3. 从零到一用 Atlas 把一个订单库管起来3.1 安装与环境准备安装很简单官方推荐的方式是用脚本装也可以从 GitHub Releases 直接下载二进制放到$PATH里。以 macOS 为例brew install ariga/tap/atlasLinux 上如果有人偏好手动安装下载完压缩包解压以后把atlas二进制放到/usr/local/bin就行。装完之后跑一下atlas version能正常输出版本号环境就算准备好了。我用的是 MySQL 8.0也配好了order_system这个库。下面所有命令都以这个库为例。如果要用 PostgreSQL把数据库 URL 换成postgres://格式即可原理完全一样。3.2 定义 schema.hcl 和 env.hcl在实际项目里我建议不要把数据库连接地址和每次命令的参数都写在命令行里而是在项目根目录放一个atlas.hcl把环境配置固化下来。举个例子env local { src file://schema.hcl url mysql://root:passlocalhost:3306/order_system dev docker://mysql/8/order_system } env production { src file://schema.hcl url mysql://root:${PROD_DB_PASS}prod-host:3306/order_system dev docker://mysql/8/order_system }这里定义了两个环境local和production。生产库的密码用环境变量${PROD_DB_PASS}引用避免把敏感信息硬编码进仓库。有了这个文件后面的命令都可以简化只需要指定--env local或--env productionAtlas 会自动读取对应的 URL 和 Schema 文件。3.3 从老库反查 Schemaatlas schema inspect很多团队不是从零开始的库里已经跑了一堆表。这时候不急着手写 HCL先用逆向能力把现有结构抽出来。atlas schema inspect \ --url mysql://root:passlocalhost:3306/order_system \ schema.hcl执行完以后schema.hcl里会自动生成现有库的完整 Schema 描述。打开文件检查一遍看看有没有不对劲的地方。比如以前有人手建过一张临时表这张表也会被带出来。如果不想要直接在 HCL 文件里删掉对应的table块后续 diff 时 Atlas 就会认为这张表是多余的并生成删除它的 DDL。这一步相当于把历史的“既定现实”变成了代码是整个迁移改造的重要基础。如果老库里有几十张表手工翻译成 HCL 太痛苦用inspect能少写几千行。3.4 生成首个迁移文件并应用假设我给orders表新加一列shipping_address。直接在schema.hcl里给对应表补上字段column shipping_address { null true type varchar(500) }然后让 Atlas 生成迁移文件atlas migrate diff add_shipping_address \ --dir file://migrations \ --env local注意这里我没有写--to和--dev-url因为它们已经定义在atlas.hcl的env.local里了。命令行只保留最必要的信息。生成完以后去migrations/目录里看新文件内容大概是-- modify orders table ALTER TABLE orders ADD COLUMN shipping_address varchar(500) NULL;之前手写迁移时这类 ALTER 语句得自己命名、自己排序、自己记录执行状态现在全自动了。接着执行atlas migrate apply \ --dir file://migrations \ --env local它会应用所有新迁移。执行后可以看到Migrating version 20250302120000数据库的atlas_schema_revisions表里也多了对应记录。此后每一次改动都走这个流程改 HCL →atlas migrate diff→ 审查 SQL →atlas migrate apply。3.5 存量老库怎么接入用 baseline 别硬跑如果你是从已有数据库开始用 Atlas最怕的是库里已经有几十条变更历史但又没有对应的迁移文件。硬生成一个init迁移去跑数据库会因为表已存在而报错。这时候要用到--baseline参数把当前状态标记为“基线”而不实际执行这些 SQL。atlas migrate diff init \ --dir file://migrations \ --env local \ --baseline V1它会生成一个名为V1的基线迁移并把当前库标记成已经应用过这个版本。之后你新加的 HCL 变更都会基于这个基线生成增量迁移。这个操作对线上库尤其友好不会动现有表结构只是建立“档案”。4. 常见问题与排查技巧生产环境踩坑实录4.1 最让我头疼的 5 个问题用 Atlas 这段时间我遇到过不少坑整理成速查表供参考现象可能原因排查思路连接数据库报dial tcp超时URL 写错或网络不通先单独用客户端连一次确认端口可达diff 后生成的 SQL 和自己预期不符dev-url 状态不干净换一个全新空库重跑 diffchecksum mismatch错误有人改动历史迁移文件对比atlas.sum找出改过的文件并还原table already exists报错重复执行了 init 迁移用--baseline重建基线或手动清掉迁移记录表生产 apply 时权限不足数据库账号缺 DDL 权限确认账号有ALTER、CREATE、INDEX、DROP权限第二点我要重点说--dev-url一旦复用里面残留的表会影响 diff 结果。我建议要么每次用全新的临时库要么创建完用完就删。Docker 模式下用docker://mysql/8/xxx会临时创建容器用完销毁基本不存在这个问题。另一个高频坑是 URL 里的特殊字符。数据库密码如果包含、#、?等字符直接拼 URL 会被解析错。建议先做 URL 编码比如密码是pssw0rd写成p%40ssw0rd。或者用atlas.hcl里的环境变量方式管理避免在命令行里裸奔。4.2 用 migrate lint 在 CI 里拦截破坏性变更如果团队成员多最好在代码评审阶段就把风险迁移拦住。Atlas 提供了atlas migrate lint能够分析最新迁移识别出几类危险操作删除表或删除列修改列类型导致数据截断添加唯一约束到已有大量数据的表直接在非空列上新增无默认值字段。把它接进 CI 流水线是性价比很高的做法。我在 GitHub Actions 里的用法大致是- name: Run Atlas Lint run: | atlas migrate lint \ --dir file://migrations \ --dev-url docker://mysql/8/order_system \ --latest 1--latest 1表示只检查最近一个新增迁移。如果 lint 报的是没有down.sql或存在破坏性变更PR 就不能合并。这种自动护栏比我盯着每个人的提交要靠谱得多。4.3 和其他 ORM 共存的经验现在很多项目会同时使用 ORM比如 GORM、TypeORM 或 ActiveRecord。这里有一个兼容性问题ORM 通常也会维护一套 Schema 定义或迁移文件如果两边同时管同一批表必然冲突。我目前的团队是这样分工的业务表结构交给 Atlas 管所有 DDL 变更以schema.hcl为准ORM 里只保留领域模型不再让 ORM 自动AutoMigrate涉及视图、存储过程、触发器这类 ORM 不擅长管理的对象也全部进 Atlas如果 ORM 自动建了临时表或者额外索引revoke 掉对应数据库账号的 DDL 权限逼迫所有变更走统一通道。这个策略执行了快一年最明显的变化是因为两边 Schema 不一致导致的线上 API 报错基本绝迹了。5. 给自己定的小规矩Atlas 使用避坑清单工具是好工具但用不好照样会出事。我整理了几条自己长期执行的规矩供参考。第一schema.hcl 永远只描述目标状态不要在里面写“中间过程”。很多人会顺手把历史遗留字段也写进去结果 diff 变成全量比对生成的 SQL 又长又难审。第二迁移文件生成后必须人工过一遍。Atlas 生成的 DDL 再准也要看它是不是符合业务预期。比如大表加列可能要配合ONLINE DDL工具或锁表策略这些 Atlas 不一定能自动判断。宁可多花十分钟审查也别直接 apply。第三生产环境不要用--auto-approve。本地测试可以用生产必须把生成的 SQL 交给 DBA 或同事 review 后再执行。虽然 Atlas 执行很快但有些破坏性操作一旦执行就是不可逆的。第四定期做一次从零重建验证。用 CI 任务检查用atlas migrate apply在一个全新空库上跑完整迁移历史再拿atlas schema inspect和schema.hcl对一次。如果两边不一致说明迁移文件缺失或顺序有问题趁早发现。最后迁移文件是团队的公共资产。不要因为顺手就本地手动改migrations/目录里的文件改动必须走 MR触发 CI 校验经过同事 review。这听起来像流程约束实际是保护所有人。我自己现在不管项目多紧急都会先把迁移文件拿去 lint 过一遍再合并。Atlas 把我以前靠自觉完成的纪律真正固化到了工具里。如果你也正被数据库迁移折腾得够呛拿一个不太核心的库先试点一周应该很快能体会到这种“把 Schema 变成代码”到底有多省心。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →