资讯详情

资讯详情

Atlas工具实战:用Schema as Code统一管理数据库迁移

最近在整理项目的数据库变更流程时被一个名字叫“atlas”的工具圈粉了。一开始看到这个名字我以为是地图册或者某个地理信息组件点进去才发现这是 ariga 出品的开源数据库 Schema 管理工具主打“Schema as Code”简单说就是让你像管理代码一样管理数据库结构。这篇文章我不打算讲官方文档里抄来的概念而是从实际项目里怎么把它用起来、踩过哪些坑、替代了哪些旧方案这个角度把 Atlas 讲透。如果你现在还在靠手写一堆迁移 SQL 维护表结构或者被 ORM 自动同步坑到心力交瘁又或者每次上线前都要提心吊胆地核对“这个 prod 库到底和 dev 差了几个字段”那这个工具大概率能帮你省掉不少事。1. 先说清楚Atlas 不是地图册是数据库 Schema 管理工具1.1 为什么我手里一堆迁移脚本还是会出问题我之前维护过一个老项目数据库表结构变更全靠手写 SQL 迁移目录结构大概是migrations/20240101_add_users_table.sql这种。刚开始还好人少、变更少大家按顺序执行就行。但一旦多个功能并行开发问题就来了两个分支各自加了迁移脚本编号还可能冲突合并的时候根本不知道该先跑哪个。同一个需求在 dev 库反复测试手改了几次表结构但迁移脚本没跟上等上生产时就靠 DBA 手工补列。一旦线上库和迁移脚本不一致没人知道要从哪一步开始修复只能靠人肉比对information_schema。后来换成 ORM 自带的 migrate 功能稍微好一点但 ORM 的迁移是绑死在语言生态里的。项目里 Java 服务用 HibernatePython 服务用 SQLAlchemy两套迁移体系互相看不懂数据库也没法统一管控。而且 ORM 自动生成的迁移经常包含一些隐式变更比如字段长度调整、索引命名不规则代码审查看不到这些细节上了生产才炸。所以我的核心痛点不是“没有迁移工具”而是缺少一个“和具体语言无关、能从数据库真实状态出发、并且能在 CI 里做变更检查”的独立层。1.2 Atlas 的核心设计思路把数据库状态当代码Atlas 的核心思路跟传统迁移工具不太一样。传统工具比如 Flyway、Liquibase是让你把“每一步怎么改”写下来是一串命令序列Atlas 是让你声明“最终想要的数据库长什么样”然后它自己计算从当前状态到目标状态需要执行哪些 DDL。这里有个非常关键的词期望状态desired state。你可以用两种方式定义这个期望状态一种是 HCL 格式的表结构描述文件另一种是标准的 SQL DDL 文件。Atlas 会把当前数据库的真实状态通过连接数据库读取 information_schema和期望状态做对比自动生成差异 SQL。这个设计带来的好处很明显不需要手动维护增量迁移文件表结构文档永远和代码仓库里的定义一致多环境漂移问题也好排查了只要跑一遍 diff哪里不一致一目了然。对比维度手写迁移脚本ORM 迁移Atlas与语言是否绑定否是否是否能反推当前库状态否部分能生成迁移 SQL人工自动自动变更评审/CI 集成难一般内置多数据库支持看脚本有限广Atlas 官方支持 PostgreSQL、MySQL、MariaDB、SQLite、SQL Server、ClickHouse甚至 MongoDB 也有一席之地。所以不管团队技术栈多杂数据库结构这一层可以由 Atlas 统一接管。2. 安装和初始化5 分钟先跑通一个本地 Demo2.1 安装方式选择Atlas 的安装方式很多我试过几种比较推荐这两个# 方式一官方安装脚本macOS / Linux curl -sSf https://atlasgo.sh | sh # 方式二Go 直接装 go install ariga.io/atlas/cmd/atlaslatest如果你不想污染本机环境也可以直接用 Docker 跑docker pull arigaio/atlas:latest安装完之后验证一下版本atlas version这里我额外说一句如果公司内部网络访问外网有限制Docker 镜像方式往往更可控你可以把镜像推到内部仓库CI 也统一用它。2.2 准备一个本地 PostgreSQL 并定义初始 Schema我先用一个本地 PostgreSQL 做演示。测试环境我习惯用 Docker 开一个一次性数据库docker run --name atlas-demo \ -e POSTGRES_PASSWORDpass \ -e POSTGRES_DBapp \ -p 5432:5432 \ -d postgres:15然后写一个最简单的 SQL 定义文件。这是 Atlas 支持的“SQL 即期望状态”的写法-- schema.sql CREATE TABLE users ( id BIGINT GENERATED BY DEFAULT AS IDENTITY, email VARCHAR(255) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), PRIMARY KEY (id), CONSTRAINT users_email_key UNIQUE (email) );2.3 用 inspect 和 diff 先验证连接项目初期我先跑一下inspect把现有空库的实际结构抓出来看看atlas schema inspect \ -u postgres://postgres:passlocalhost:5432/app?search_pathpublic正常会输出一个空的 schema 信息。如果连接串没问题再跑一次diffatlas schema diff \ --from postgres://postgres:passlocalhost:5432/app?search_pathpublic \ --to file://schema.sql \ --dev-url docker://postgres/15这里重点解释一下--dev-url这是 Atlas 里最容易忽略又最重要的参数。它指定一个临时的开发库Atlas 会在里面模拟“当前库和期望库做对比”的过程避免把风险直接带到目标库上。官方推荐直接用 Docker 起一个临时数据库用完就丢非常方便。如果 diff 结果符合预期它会把需要执行的 SQL 打印出来例如CREATE TABLE users (...);这个命令不会真改数据只是展示差异。看到这一步说明基本链路已经通了。3. 两种工作流声明式迁移和版本化迁移怎么选3.1 声明式迁移适合开发环境和快速实验声明式迁移的工作方式很简单改schema.sql然后执行apply。atlas schema apply \ -u postgres://postgres:passlocalhost:5432/app?search_pathpublic \ --to file://schema.sql \ --dev-url docker://postgres/15apply会直接计算差异并执行 DDL。如果加了--dry-run它只打印 SQL 不执行这个参数在 CI 和本地联调时特别有用。声明式迁移最爽的场景是开发环境下表结构说改就改不需要生成一堆迁移文件。比如本地起依赖库一条命令就把所有表拉起来比跑几十个脚本快多了。但它不适合生产环境因为生产数据库的变更需要审批、需要留痕、需要可回滚。声明式迁移没有明确的“版本”概念改完就完了很难回答“上个版本结构长什么样”这种问题。3.2 版本化迁移才是生产环境的正经做法生产环境我推荐走版本化迁移。这个模式的行为更像 Flyway但生成迁移文件的过程是自动的。先把当前 schema 作为基线版本生成一份迁移atlas migrate diff init \ --dir file://migrations \ --to file://schema.sql \ --dev-url docker://postgres/15执行后会在migrations/目录生成类似20250101000000_init.sql的文件同时生成一个atlas.sum校验文件。之后每次改表结构流程就是修改schema.sql或schema.hcl。重新跑atlas migrate diff自动生成增量迁移 SQL。在 CI 里跑 lint 和校验。部署时执行atlas migrate apply。比如我再加一张订单表同时给 users 表加个nickname字段修改 schema 后执行atlas migrate diff add_orders_and_nickname \ --dir file://migrations \ --to file://schema.sql \ --dev-url docker://postgres/15Atlas 会生成一个新的迁移文件内容类似CREATE TABLE orders ( id BIGINT GENERATED BY DEFAULT AS IDENTITY, user_id BIGINT NOT NULL, amount DECIMAL(10,2) NOT NULL, PRIMARY KEY (id) ); ALTER TABLE users ADD COLUMN nickname VARCHAR(100);生成完记得打开文件看一遍。我见过有人太信任自动生成结果连 drop 语句都直接放进生产迁移记录里这很危险。自动生成帮你省事但最终拍板的一定要过人眼。执行迁移atlas migrate apply \ --dir file://migrations \ --url postgres://postgres:passlocalhost:5432/app?search_pathpublic它会检查当前数据库已经跑到哪个版本然后按顺序执行未应用的迁移文件。atlas.sum会校验所有迁移文件是否被改动过一旦有人手工修改历史迁移文件校验就会失败从机制上防止“篡改历史”。3.3 迁移 lint让 CI 拦住破坏性变更版本化迁移生成之后真正体现专业度的是加一层变更检查。Atlas 内置了migrate lint可以检查迁移是否存在破坏性风险比如删列、改类型、加非空约束但没给默认值。命令行直接跑atlas migrate lint \ --dir file://migrations \ --dev-url docker://postgres/15它会把 lint 结果分INFO、WARNING、ERROR三个级别输出。举个例子如果我生成了一条删列迁移lint 很可能给出类似这样的提示DestructiveChange: dropping column users.email如果你想在 CI 里让这种变更直接失败可以在atlas.hcl里配置策略lint { destructive { level ERROR } }还可以配置更细的规则比如禁止创建外键、禁止修改列类型等。对于规范化要求高的团队这些规则比人肉 code review 靠谱得多因为人总会漏看。4. 多环境管理与 CI 集成4.1 用 env 块区分 dev、staging、prod项目稍微复杂一点就会有多个数据库环境。Atlas 支持在atlas.hcl里声明多个env块这样不用每次敲一长串连接串。env local { url postgres://postgres:passlocalhost:5432/app?search_pathpublic dev docker://postgres/15 } env prod { url postgres://deploy:${PROD_DB_PASSWORD}prod-host:5432/app?search_pathpublic dev docker://postgres/15 }注意我用了${PROD_DB_PASSWORD}环境变量占位生产密码不要明文写进文件这应该是不用提醒的基本素养。后续执行atlas migrate apply --env prod你还可以继续在 env 块里配置迁移目录、lint 策略、格式等团队约定越统一协作成本越低。4.2 在 GitHub Actions 里做变更检查CI 集成是我觉得 Atlas 比纯手写迁移脚本强很多的另一个地方。下面是一个最小可用的 GitHub Actions 配置作用是在每次 PR 和 push 时跑迁移 lint 和校验name: migrate-check on: pull_request: push: branches: [main] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Setup Atlas uses: ariga/setup-atlasv0 - name: Analyze migration run: | atlas migrate lint \ --dir file://migrations \ --dev-url ${{ secrets.DEV_DATABASE_URL }}这里secrets.DEV_DATABASE_URL是连到开发库的连接串开发库可以是一个短暂存在的数据库实例。跑完 lint 之后如果还想验证迁移能否在干净库上完整执行可以再起一个 postgres 容器从头apply一遍相当于集成测试。CI 里加了这层检查之后我最大的感受是“表结构变更不再靠记忆而是靠工具兜底”。以前 code review 很难发现一个迁移文件是否会导致数据丢失现在 CI 直接就把破坏性变更标红了。5. 常见问题与踩坑实录5.1 不同数据库的连接串踩坑Atlas 官方支持不少数据库但连接串参数对结果影响很大。拿 PostgreSQL 来说如果库里有多个 schema连接串里没加search_pathAtlas 默认可能只处理public导致你以为 diff 是全量的结果它根本没看到其他 schema。MySQL 也有类似情况你要指定charset和parseTime吗其实 Atlas 更关心的是 DSN 里的tls配置。如果数据库开了 SSL连接串没跟上直接握手失败。我建议项目初期就把连接串放到atlas.hcl里统一管理同时写清楚每个参数的用途不然过两个月你自己都忘了这个串是什么意思。5.2 dev-url 没配导致的“意外”我在一个内部工具里见过有人直接拿生产库跑schema apply没配dev-url。他本意是 diff 一下结果工具某些场景下会把差异直接执行上去。虽然 Atlas 在交互模式下会二次确认但人在连续加班的时候真的会盲目回车。所以我的规矩是本地开发永远用一个 Docker 临时库作为dev-url。任何指向生产库的apply必须带--dry-run先看 SQL。能不在生产环境执行 DDL 就不手动执行尽量走 CI 流程。5.3 atlas.sum 校验失败和多人协作冲突版本化迁移用久了多人协作时atlas.sum校验失败是常见的。原因是 Atlas 会为每个迁移文件生成校验和如果你直接改了一个已经提交的迁移 SQL或者用工具合并代码时把数据库目录弄乱了校验就会挂。遇到这种情况我的处理方案是确认有没有人真的需要修改历史迁移正常情况下不应该改。如果是分支合并带来的重复迁移文件保留一份删掉另一份然后重新执行atlas migrate hash生成新的atlas.sum。在 PR 描述里明确说明改动原因最好带上 reconfigure 前后的 diff。千万不要直接删掉atlas.sum再跑一遍因为那一瞬间你就失去了对迁移文件完整性的保护后面有人偷偷改迁移内容你也察觉不到。5.4 大表 DDL 仍然会锁表这是很多人容易高估 Atlas 的一个点。Atlas 负责的是“生成并执行迁移 SQL”但它不会帮你把 MySQL 大表加索引从锁表变成在线变更底层数据库的行为不会因为换了个工具就改变。我之前在 MySQL 5.7 上对一个千万级表加索引执行迁移时主库的写入直接卡住主从延迟飙高最后是半夜低峰期加上用 gh-ost 才搞定。如果你的业务表已经很大建议先看看目标数据库支持不支持 Online DDL或者把 Atlas 生成的大表变更交给专门的在线变更工具处理。Atlas 自己也在文档里强调过这一点它是结构管理工具不是数据库内核救世主。5.5 权限与安全策略最后说下权限。Atlas 执行迁移时连接数据库的账号必须要足够权限但它并不需要超级管理员。最小权限原则下建议给 Atlas 单独建一个账号只授予 DDL 相关权限和读取元数据的权限。如果你把 Atlas 集成进 CI最好限制触发条件只有 main 分支或打了 tag 的版本才能对生产库执行applyPR 里只跑 lint 和校验。生产数据库密码放进 CI 的 secrets 或云厂商的密钥管理服务不要直接写在 workflow 文件里。以我的个人经验越是大项目越需要一个独立的、不受业务代码语言限制的数据库结构管理层。Atlas 目前是我用过所有方案里把“期望状态”和“实际状态”这对矛盾处理得最顺手的一个。你在本地只需要先跑通一个 demo后面自然会上瘾。最后再分享一个小建议刚开始迁移到 Atlas 的时候别急着把所有存量项目都切过来先拿一个内部服务试试跑通基线、lint、CI 之后再推广。等到你和团队都习惯“改表先改 schema 文件”这个节奏再回头看那些手写迁移脚本的岁月你大概率会感谢当初的决定。
觉得有用,分享给同行:

为您的企业打造数字门面

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

立即咨询 →