Atlas:从 Schema Diff 到可审计的数据库变更链
一个订单服务在发布后开始持续报错:新代码会写入 orders.delivery_note,生产库却没有这个字段。开发者确认 ORM 模型已经合并,流水线也显示部署成功;数据库管理员则在另一张工单中看到了同名 DDL,但它从未进入当前环境。更麻烦的是,一位同事为了“补齐差异”直接执行了 ALTER TABLE,另一位同事又修改了已经在测试环境执行过的迁移文件。此时仓库、迁移目录、数据库真实结构和发布记录分别讲着不同的故事。
Atlas 处理的不是“怎样写一条 ALTER TABLE”,而是怎样让数据库变化拥有可核对的来源和状态。它把数据库结构读成 schema graph,将期望结构、迁移目录或 ORM 模型装载成另一张 graph,再计算变更集合;开发数据库负责把 SQL 交给真实数据库方言验证,迁移目录负责固化执行顺序,revision table 记录某个目标库已经走到哪里。只有这些对象能互相证明,团队才能回答:这次变化从哪里来、会执行什么、是否改过历史、目标库是否漂移、失败后从哪个状态恢复。
先决定谁是事实源,再选择工作流
Atlas 提供声明式和版本化两种工作流。它们共享 inspect、diff 和 dev database 等机制,但控制时机不同。
声明式工作流保存“最终应该长什么样”。atlas schema apply 每次检查目标库当前结构,与 SQL、HCL、ORM 或另一数据库表示的期望状态比较,现场生成计划,经确认后执行。它适合个人开发库、短生命周期环境和能够在执行时完成审查的受控场景。优点是意图集中、迭代快;风险是计划受目标库实时状态影响,同一份 schema 面对两个已经漂移的环境可能生成不同 SQL。
版本化工作流保存“按照什么顺序走到那里”。开发者先用 atlas migrate diff 将期望结构与迁移目录代表的末态比较,生成有序 SQL 文件;CI 对文件和模拟执行结果做检查,交付阶段再用 atlas migrate apply 顺序执行待处理版本。它适合多人协作、需要审批、离线交付、数据库随应用制品一起发布的场景。代价是迁移历史必须保持线性,结构变化和数据搬迁需要更严格的阶段设计。
多数团队不必二选一:本机开发库用声明式 apply 快速试验,准备合并时生成版本化迁移;共享环境只消费经过评审的迁移目录。这个组合把“开发反馈速度”和“交付可重复性”放在不同阶段解决。Atlas 的两种工作流说明也强调,声明式与版本化可以组合使用,而不是互斥产品模式。
安装入口决定许可、驱动和供应链边界
Atlas CLI 可以使用安装脚本、Homebrew、Windows 独立二进制或 arigaio/atlas 容器。下载入口和校验摘要应从 Atlas 安装页取得,CI 固定经过组织批准的版本或镜像 digest,不要每次拉取 latest。安装后先记录可执行文件身份:
Get-Command atlas | Format-List Source,Version
atlas version
atlas helpmacOS 可以使用官方 tap,Linux 可以先审阅下载脚本再执行:
brew install ariga/tap/atlas
curl -fsSLo atlas-install.sh https://atlasgo.sh
less atlas-install.sh
sh atlas-install.sh
atlas versionWindows 从安装页链接的 AMD64 二进制下载后,应校验同页给出的 SHA-256,再把 atlas.exe 放进组织批准的工具目录并加入 PATH。不要从即时通讯附件分发可执行文件,也不要让个人下载目录成为 CI 的工具源。
容器入口适合临时验证和不可变 runner。下面只读取当前目录并显示帮助,不接触数据库:
docker pull arigaio/atlas@sha256:REPLACE_WITH_APPROVED_DIGEST
docker run --rm arigaio/atlas@sha256:REPLACE_WITH_APPROVED_DIGEST version标准发行二进制与 Atlas Community Edition 的许可和能力入口不同:标准发行版采用 Atlas EULA,Community Edition 的源码与社区构建采用 Apache 2.0。Community Edition 能力表显示,它保留 MySQL、PostgreSQL、SQLite、MariaDB 的核心 schema 管理,以及 migrate diff、apply、status 等版本化迁移能力;它不包含 migrate lint,也不包含 checkpoint、down、rebase、edit、rm、迁移测试、Registry、漂移检测、审批策略和高级数据库对象支持。标准发行版中的部分治理能力还需要 atlas login 解锁 Pro,例如 v0.38 起的迁移 lint、pre-migration checks 和同步的 pre-apply drift detection。选型不能只看命令名或网页演示,而要记录安装来源、CLI 构建、许可证、登录状态和实际命令输出;否则 CI 可能把一个根本不存在的检查写成已经生效。
额外数据库驱动也是发行边界的一部分。SQL Server、ClickHouse、Redshift、Oracle、Spanner、Snowflake 等不在 Community Edition 的驱动集合内;采用标准发行版时仍要按目标驱动说明验证支持对象、认证和托管平台限制。开发机能执行 atlas version,只证明二进制可启动,不能证明目标驱动、Docker、代理、CA、登录能力或数据库权限已经可用。
用 SQLite 跑通不接触共享库的完整闭环
先用 SQLite 文件验证工作流,可以把权限和网络变量降到最低。目录只需要四类对象:期望结构、Atlas 环境配置、迁移目录和本地目标库。
database/
atlas.hcl
schema.sql
migrations/
declarative.db
versioned.dbschema.sql 保存期望状态:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);atlas.hcl 把事实源、开发库和迁移目录绑定到同一个环境:
env "declarative" {
url = "sqlite://declarative.db?_fk=1"
dev = "sqlite://dev?mode=memory&_fk=1"
schema {
src = "file://schema.sql"
}
}
env "versioned" {
url = "sqlite://versioned.db?_fk=1"
dev = "sqlite://dev?mode=memory&_fk=1"
schema {
src = "file://schema.sql"
}
migration {
dir = "file://migrations"
}
}url 是会被 inspect 或 apply 的目标,dev 是 Atlas 用来重放结构和验证 SQL 的空白环境,schema.src 是期望状态,migration.dir 是版本化历史。两个环境故意使用不同目标库:declarative.db 展示按期望状态直接收敛,versioned.db 展示从空库消费迁移历史。如果先对一个库执行声明式 apply,再把“创建相同对象”的首个版本化迁移应用到同一个库,revision 仍为空而对象已经存在,首次 versioned apply 会报重复对象;不能用 baseline 或改 revision 表掩盖这个实验设计错误。开发数据库还必须与目标数据库使用相同引擎和兼容版本;SQLite 内存库只能验证 SQLite 方言,不能证明 MySQL 的隐式提交、PostgreSQL 的锁行为或某个云数据库的扩展限制。
先观察空库与期望结构的差异,不执行变更:
Set-Location database
atlas schema inspect --url "sqlite://declarative.db?_fk=1"
atlas schema diff `
--from "sqlite://declarative.db?_fk=1" `
--to "file://schema.sql" `
--dev-url "sqlite://dev?mode=memory&_fk=1"schema inspect 对空库不会列出 users。schema diff 应输出 CREATE TABLE users 和唯一约束;它不应修改 declarative.db 的结构。如果输出包含仓库之外的表,先检查 URL 的数据库或 schema scope,不能用 --exclude 草率隐藏未知对象。
声明式正向实验先 dry-run,再交互 apply:
atlas schema apply --env declarative --dry-run
atlas schema apply --env declarative
atlas schema inspect --url "sqlite://declarative.db?_fk=1"
atlas schema apply --env declarative --dry-run第一次 dry-run 应显示建表计划;交互确认后,inspect 应显示 users 的四个字段和索引;最后一次 dry-run 应显示 schema 已同步、没有待执行变化。这个“第二次计划为空”比第一次命令退出码更重要,它证明 apply 后的数据库能够被同一份事实源重新解释。
清理本地实验前先确认目标路径,再删除文件:
Resolve-Path .\declarative.db
Remove-Item .\declarative.db
Test-Path .\declarative.dbTest-Path 应返回 False。不要在删除后再用数据库 URL 执行 inspect 作为确认;SQLite 驱动可能重新创建一个空文件,让“清理验证”本身留下新目标库。
atlas schema clean 会清除受目标 scope 覆盖的数据库对象,不能成为共享环境的日常重置命令。共享库、测试库和生产库应在权限层拒绝 clean 所需的破坏性权限,而不是只靠操作手册提醒。
Dev Database 是编译与模拟工作区,不是随便找的测试库
Atlas 需要开发数据库来归一化数据库方言、重放迁移目录、计算 diff,并提前暴露只有数据库引擎能判断的错误。例如表达式索引、默认值、检查约束和隐式类型转换在纯文本比较中可能看起来合法,装载到真实方言后才会失败。Dev Database 机制展示了一个典型差异:缺少开发库时,无效约束可能在目标库执行中途才报错;使用同方言开发库后,错误会在计划阶段暴露。
MySQL 或 PostgreSQL 项目可以使用 Atlas 的 Docker URL,让 CLI 创建一次性开发容器:
env "local" {
url = getenv("DATABASE_URL")
dev = "docker://postgres/16/dev?search_path=public"
schema {
src = "file://schema.sql"
}
migration {
dir = "file://migrations"
}
}这里有四条不可妥协的边界:
dev 必须是空白、可丢弃、与目标同方言的数据库,不能指向共享测试库。schema scope 必须一致。PostgreSQL 的 search_path=public 与数据库级 URL 产生的对象范围不同;MySQL URL 是否带数据库名也会改变限定名和管理范围。同一个开发数据库不能被并发任务共享。官方说明 Atlas 会清理自己的临时对象,但并发使用仍会互相覆盖中间状态。
目标版本差异必须进入验证矩阵。用 PostgreSQL 16 的 dev database 为另一个主版本或带特殊扩展的托管库计算计划,只能作为近似验证。
企业代理环境常见的现象是 Atlas 能启动,却在 docker:// 开发库阶段超时。此时分别检查 Docker daemon 拉镜像所用代理、CLI 到 registry/Cloud 的代理、容器 DNS,以及企业 CA 是否进入 CLI 和数据库驱动的信任链。不要把数据库连接串打印进 --log-level debug 的公开日志。
把期望结构冻结成版本化迁移
声明式实验稳定后,用 migrate diff 生成可评审的 SQL:
atlas migrate diff create_users --env versioned
Get-ChildItem .\migrations
Get-Content .\migrations\atlas.sum
atlas migrate validate --env versioned
atlas migrate apply --env versioned --dry-run
atlas migrate apply --env versioned
atlas migrate status --env versioned生成结果应包含一个形如 <version>_create_users.sql 的文件和 atlas.sum。版本默认采用可排序时间戳,标签描述意图;团队不应手工制造重复版本,也不应依赖开发机时间解决并行冲突。migrate validate 检查目录的基本有效性与完整性,apply 会在空白的 versioned.db 中创建业务对象和 revision table 并记录已执行版本;migrate status 应显示没有 pending migration。若这里出现 table users already exists,先确认是否误把 declarative.db 配给了 versioned 环境,不要立即使用 --baseline 跳过。
确认 status 与 inspect 证据已经保存后,版本化实验库也要按明确路径清理;迁移目录和 atlas.sum 是要进入版本控制的制品,不随数据库文件删除:
Resolve-Path .\versioned.db
Remove-Item .\versioned.db
Test-Path .\versioned.dbatlas.sum 不是普通文件列表。Atlas 用反向单分支 Merkle hash tree 记录每个迁移文件和整个目录的完整性。修改旧文件、插入历史版本或解决并行合并时,哈希会变化。它的价值不是防止恶意提交者同时改 SQL 和哈希,而是让无意的历史改写、排序冲突和缺失文件在代码审查与 CI 中留下证据,详见迁移目录完整性说明。
反向实验:修改已经生成的迁移
先复制实验目录,再修改迁移 SQL 中的一处内容:
$lab = Join-Path $env:TEMP "atlas-hash-lab"
Remove-Item $lab -Recurse -Force -ErrorAction SilentlyContinue
Copy-Item . $lab -Recurse
Set-Location $lab
$migration = Get-ChildItem .\migrations\*.sql | Select-Object -First 1
Add-Content $migration.FullName "-- unauthorized history edit"
atlas migrate validate --env versioned预期是完整性检查失败,并指出 atlas.sum 与目录内容不一致。不要立即运行 atlas migrate hash 抹掉证据。先判断这份迁移是否已经进入任何共享环境:
尚未合并、尚未执行,可以在当前分支重新生成或有理由地更新哈希。已合并但未执行,应通过新提交修复,并让所有消费者重新同步目录。已在任一环境执行,默认做法是保留旧文件,新增 roll-forward 迁移;修改历史会让不同环境拥有同一版本号却执行不同 SQL。
atlas migrate hash 只重算完整性文件,不验证改写历史在业务上正确,也不会修改已经写入目标库的 revision 和 schema。把它当作“修复 migrate 报错”的通用命令,会把审计信号消掉。
多人并行生成迁移时,两个分支各自追加文件并更新 atlas.sum,合并通常会产生哈希冲突。正确流程是拉取主线,在标准发行版中可用 atlas migrate rebase 重排当前分支迁移;Community Edition 没有该命令,应重新生成尚未共享的当前分支迁移。两条路径都必须重跑 diff、validate、风险检查和应用实验。不能只按文件名排序后手工拼接 atlas.sum,因为两个迁移的 SQL 可能分别正确、组合后却冲突。
Lint 必须模拟真实方言,也必须承认产品边界
atlas migrate lint 会在开发数据库上模拟迁移,并分析破坏性变化、数据依赖和兼容风险。迁移安全检查说明明确指出,自标准发行 CLI v0.38 起,该命令需要 Pro 登录;Community Edition 不提供 migrate lint。流水线必须先用 atlas version、安装来源和一个预期可用的只读检查确认实际能力,再保存真实报告。没有 Pro 能力时,应采用 migrate validate、在隔离数据库重放、数据库原生检查和组织自有策略门禁的组合;这些替代项各自证明目录完整性、SQL 可执行性或已知规则,不能在报告中统称为 Atlas lint。
具备能力后,拉取请求可以只检查相对于主线新增的迁移;开发库必须与目标数据库同方言:
atlas migrate lint `
--dir "file://migrations" `
--dev-url "docker://postgres/16/dev?search_path=public" `
--latest 1--latest 1 是教学用的单迁移入口,不是团队基线。真实 CI 应根据合并基线识别全部新增 changeset,防止一个 PR 添加两个文件却只检查最后一个。lint 报告也不是“允许上线”的证明:它不知道表的真实行数、热点时段、长事务、复制拓扑、云厂商 DDL 算法、应用兼容窗口和数据修复语义。
危险 DDL 至少再经过这些证据:
| 变化 | 静态信号 | 运行前证据 | 可接受的推进方式 |
|---|---|---|---|
DROP COLUMN / DROP TABLE | destructive change | 读写流量、代码引用、保留与恢复责任 | 先停止写入与读取,跨发布观察,再删结构 |
NOT NULL / 新约束 | data dependent | 不满足条件的行数必须为零 | 先回填和持续校验,再单独加约束 |
| 大表类型变更 | rewrite / lock | 表大小、锁等待、复制延迟、引擎算法 | 影子表或在线 DDL,设置停止阈值 |
| 唯一索引 | duplicate risk | 冲突样本和重复计数 | 清理数据后创建,并保留失败键证据 |
| 默认值与回填混写 | long transaction | 更新批次、WAL/binlog 增长、磁盘余量 | 拆成结构、分批回填、约束三阶段 |
Plan、apply 与 revision table 构成执行证据
版本化 apply 不是重新计算业务意图,而是读取迁移目录,检查完整性和 revision,按顺序执行 pending 文件。默认事务行为还受数据库 DDL 能力与 --tx-mode 影响。file、all、none 不是性能开关:它们决定失败时哪些语句能一起回滚。MySQL 的许多 DDL 不能像 PostgreSQL 事务 DDL 那样依赖原子回滚;即使 Atlas 返回失败,前面的结构也可能已经生效。
共享环境的执行链应分三段:
atlas migrate validate --env staging
atlas migrate apply --env staging --dry-run
atlas migrate status --env staging第一段证明目录未损坏,第二段显示待执行文件和 SQL,第三段核对目标库当前 revision。真正 apply 由受保护 runner 执行,随后再次运行 status、schema inspect 和业务兼容探针。--dry-run 不等于完全不访问目标库:Atlas 仍需读取 revision 和数据库状态;若使用 Pro 的 pre-migration checks,这些检查还会在目标库执行。dry-run 身份因此仍应使用只满足检查所需的权限,并限制超时和资源占用,不能因为“没有执行 DDL”就给 PR runner 生产写凭据。
revision table 回答“Atlas 记录哪些版本已执行”,不回答“真实 schema 一定与这些版本一致”。控制台手改、失败后人工补 SQL、错误 baseline 和修改历史都会造成 revision 与 schema 分离。版本化工作流依赖“目标没有绕开 Atlas 漂移”这一假设。pre-apply drift check 会在 migrate apply 开始时把目标结构与 Registry 中相应版本的预期状态比较,这属于 Pro 能力;Atlas Cloud 的持续漂移监测则是异步控制面,私网数据库通常还需要 Atlas Agent 建立可达性。两者不能互相冒充,也不能只凭 revision 代替。离线团队应定期把迁移目录重放成末态,再与只读 inspect 结果做 diff,并对差异开工单,而不是自动覆盖。
Baseline 是接管点,不是跳过错误的按钮
接管已有数据库时,先只读 inspect 出真实结构,整理为 SQL/HCL/ORM 事实源,再生成 baseline migration。已有数据库已经处于该结构,首次 apply 使用 baseline 版本把它标记为已执行;全新数据库则真正执行 baseline SQL。官方导入与 baseline 指南明确区分这两种情况。
atlas schema inspect `
--url $env:READONLY_DATABASE_URL `
--format '{{ sql . }}' > schema.sql
atlas migrate diff baseline `
--to "file://schema.sql" `
--dev-url "docker://postgres/16/dev?search_path=public" `
--dir "file://migrations"
atlas migrate apply `
--url $env:DATABASE_URL `
--dir "file://migrations" `
--baseline "REPLACE_WITH_BASELINE_VERSION"执行 baseline 前必须证明目标结构与 baseline 表达的状态一致。只因数据库“不干净”就使用 --allow-dirty,会让 Atlas 在未知对象旁执行迁移;只因迁移报重复对象就提高 baseline 版本,会让未执行的变化被伪装成已完成。接管检查至少覆盖 schema scope、扩展、触发器、函数、权限对象、命名规则、人工 DDL、数据修复历史和每个环境的实际差异。
ORM 负责模型表达,Atlas 负责数据库变化,并不自动消除双重事实源
Atlas 可以通过 external schema provider 读取 Ent、GORM、SQLAlchemy、Django、Prisma 等 ORM 模型,再参与 inspect、diff 或 migrate diff。一个稳妥的流水线是:用锁定依赖的构建环境加载 ORM schema,生成 Atlas graph,与迁移目录末态比较,输出 SQL 供评审;运行时只应用版本化迁移,不让应用启动时再执行 ORM auto-migrate。
atlas.hcl 中的外部程序是供应链和执行边界:
data "external_schema" "orm" {
program = [
"npm",
"run",
"schema:print"
]
}
env "ci" {
src = data.external_schema.orm.url
dev = "docker://postgres/16/dev?search_path=public"
migration {
dir = "file://migrations"
}
}这个程序会在开发机或 runner 上执行,因此依赖安装脚本、环境变量、网络出口和工作目录都要受控。schema loader 不应读取生产数据库凭据,也不应在生成结构时执行业务初始化。ORM 模型表达不了的扩展、触发器、RLS 或函数,可能需要 SQL/HCL 与模型组合;Atlas 的 composite schema 属于 Pro 能力。若组织不采用该能力,应选定唯一的人工补充入口,并在 CI 检查 ORM 结构、补充 SQL 与迁移目录三者没有漂移。
ORM 升级尤其需要双版本检查。provider 或 loader 对默认值、索引命名和类型映射的归一化变化,可能让业务模型未改却生成大量 DDL。升级 PR 应用旧版和新版分别生成 diff,比较 schema graph 与 SQL,不允许把“工具自动生成”当作跳过数据库评审的理由。
CI 将生成、审查和执行分权
高信任流水线不应让每个 pull request 拿到目标库写权限。一个可实施的分层是:
PR runner 读取代码与迁移目录,启动一次性 dev database,执行 diff 一致性、validate、lint 或替代策略,只产生受限报告。合并后构建不可变迁移制品,绑定 Git commit、Atlas 版本、迁移目录哈希、数据库方言和审查结果。环境部署 runner 使用短期数据库身份读取制品,先 status 与 dry-run,获得环境审批后 apply。
apply 后由只读验证身份执行 schema diff、业务兼容探针和 revision 核对。
GitHub Actions 可以使用 ariga/setup-atlas 和 ariga/atlas-action,但 @v1 仍是可移动标签。供应链要求较高的仓库应固定 action commit SHA,并通过 Dependabot 或组织镜像受控升级:
name: database-schema-check
on:
pull_request:
paths:
- "database/schema/**"
- "database/migrations/**"
- "database/atlas.hcl"
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@REPLACE_WITH_APPROVED_COMMIT_SHA
- uses: ariga/setup-atlas@REPLACE_WITH_APPROVED_COMMIT_SHA
with:
version: REPLACE_WITH_PINNED_VERSION
- name: Validate migration directory
working-directory: database
run: atlas migrate validate --env ci
- name: Recompute diff without writing target database
working-directory: database
shell: bash
run: |
atlas migrate diff ci-drift-check --env ci
test -z "$(git status --porcelain -- migrations)" || {
git status --short -- migrations
exit 1
}这里的 diff 可能在发现漂移时创建迁移文件并更新 atlas.sum,但不会写目标数据库;后续 git status 将这种生成结果转成 CI 失败证据。runner 结束后工作区被销毁,流水线不自动提交生成文件。开发者应在自己的分支重新生成、审查并提交迁移,不能让 CI 机器人在未审查时改写历史。
具体 action 输入会随版本演进,应按 Atlas GitHub Actions 集成和锁定版本的 README 复核。需要 Cloud lint、Registry、审批或部署报告时,ATLAS_TOKEN 只放在受保护环境 secret 中;PR 来自 fork 时不得把 token 或数据库凭据暴露给不受信代码。目标数据库 URL 也不应出现在命令行回显和 artifact 中,优先由 secret manager 注入短期凭据,并关闭 shell trace。
应用发布与数据库发布的顺序由兼容窗口决定。常用的 expand-contract 是:先增加向后兼容结构,发布能同时读写新旧结构的应用,回填并观察,再停止旧字段使用,最后在另一个变更窗口删除旧结构。单次 Atlas apply 不能替代这条跨版本协议。
失败恢复从“发生在哪一层”开始
数据库变更失败不能统一归为“回滚迁移”。先分清证据所在层:
| 现象 | 第一证据 | 可能状态 | 处理顺序 |
|---|---|---|---|
migrate validate 报 checksum | atlas.sum 与 Git diff | 历史被改或合并未重排 | 停止 apply,重放主线并重生成当前迁移 |
| dev database 创建失败 | Docker、代理、CA、方言 URL | 计划尚未可信生成 | 修复开发库,不切到共享库“临时验证” |
| lint 报 destructive / data dependent | 报告中的文件、语句、对象 | SQL 可执行但业务风险未闭合 | 拆阶段、补数据证据、重新生成与复演 |
| apply 中途失败 | stderr、revision、真实 schema、数据库事务日志 | 全回滚、部分生效或未知 | 冻结后续发布,逐项核对已执行语句 |
| status 已完成但应用报缺字段 | revision 与 inspect/diff | 漂移、错误 scope 或错误目标库 | 核对目标身份,修复 schema 与发布映射 |
| 同一迁移在某环境重复对象 | 迁移历史、人工 DDL 审计 | 环境被旁路修改 | 不改旧文件,评估修正库或新增调和迁移 |
事务型数据库在 file transaction 失败时,SQL 与 revision 可能一起回滚,此时 migrate status 不一定展示那次已回滚尝试;仍要保存流水线 stderr 和数据库日志。非事务 DDL 可能留下部分结构,不能直接重跑整个文件。先用只读 inspect 确认每条语句的实际结果,再决定补齐、反向修复或从备份恢复。
标准发行版的 atlas migrate down 会根据数据库当前结构、revision 和目标版本生成并执行降级计划,也允许在 atlas:txtar 迁移中提供显式 down.sql;Community Edition 不提供该命令,Pro 审批策略又是另一层治理能力。它与事务失败时的 rollback 不是一回事:down 处理的是已经生效或部分生效后需要退出的版本,仍可能再次取得锁、删除数据,而且会从当前迁移状态向目标版本推进,不能假设数据库仍处于理想末态。生产线性历史默认仍应新增 roll-forward 版本。结构可以推导反向 DDL,数据语义通常不能:删列前丢掉的数据、错误回填、外部副作用和应用在新旧结构间写入的记录,不会因为反向建列自动恢复。
每个高风险变更应预先写出恢复决策:
停止条件:锁等待、错误率、复制延迟或执行时间越过环境预算
权威证据:migration version、schema diff、数据校验、备份恢复点
可逆窗口:旧应用仍兼容且旧字段尚未删除
恢复动作:停止发布、取消或终止 DDL、roll-forward、恢复备份或流量切换
数据责任:谁判断数据修复完成,谁批准重新开放写入阈值不能照抄示例数字,应来自表规模、SLO、复制拓扑、数据库变更窗口和基线演练。没有做过恢复演练的 rollback 文件,只是一段未经验证的 SQL。
权限、凭据与数据边界比 CLI 参数更重要
本地 SQLite 实验无需账号;连接共享数据库时至少拆分三类身份:inspect/diff 使用 catalog 只读权限,dev database 使用可创建和删除临时对象的隔离账号,apply 使用仅覆盖目标 schema 的受控 DDL 权限。Cloud token 管 Atlas Registry、报告或审批,数据库凭据管数据面,两者不能互相替代,也不应由同一个长期个人 secret 承担。
连接 URL 常包含用户名、密码、主机和数据库名。不要把完整 URL 写进 atlas.hcl、迁移 SQL、PR 评论或命令历史。环境配置可以从 secret manager 注入:
variable "database_url" {
type = string
default = getenv("DATABASE_URL")
}
env "staging" {
url = var.database_url
dev = "docker://postgres/16/dev?search_path=public"
migration {
dir = "file://migrations"
}
}runner 日志只记录目标环境的非敏感身份,例如集群别名、schema、迁移目录哈希和 revision;不要输出 URL。数据库审计应能关联短期身份、流水线运行和 DDL。迁移文件本身也可能泄漏敏感数据:不要把生产用户、邮箱、token 或真实样本写进回填 SQL;需要数据映射时使用受控临时表、参数化作业或独立数据修复流水线,并定义清理和留存责任。
Atlas Cloud 能提供 Registry、报告、审批、漂移检测和部署可见性,但也会引入 schema 元数据、组织账号、网络出口、token、座席/用量成本和退出迁移。启用前确认哪些结构与日志会上云、保留多久、谁可见、区域与合规要求、服务不可用时是否阻断发布,以及迁移目录能否完整导出。离线或自托管边界不能靠“暂时不用登录”推断,应按 Community Edition 与所需能力逐项验证。
架构选型看变更控制面,而不是工具功能数量
小型单体、单数据库、发布频率低的团队,可以使用仓库内版本化目录、同方言 dev database 和 CI validate,把 apply 绑定到应用部署。重点是建立唯一迁移入口和禁止应用启动自动改生产 schema。
多服务共享 schema 时,不能简单让每个仓库维护一份目录。迁移所有权应按对象或 bounded context 划分,跨域外键、公共表和执行顺序由一个集成队列仲裁。atlas.sum 能暴露目录并行冲突,却不能决定两个服务谁有权删除共享字段。
多租户 database-per-tenant 架构需要额外处理目标枚举、并发批次、失败隔离、版本分布和成本。迁移成功率不能只看总体百分比;应能列出每个租户的 revision、失败语句、重试次数和暂停原因。Atlas Cloud rollout 或自建编排都只是执行层,租户分批、跳过策略、补偿和 SLO 仍由团队定义。
强监管环境通常需要迁移制品签名、双人审批、职责分离、不可变审计和恢复演练。此时 Atlas 可以承担 schema 计算和迁移执行,但不能替代变更管理系统、数据库原生审计、备份恢复平台和应急授权。工具的作用是提供机器可验证的证据接口,而不是把所有治理职责收进一个 SaaS 页面。
成本应计算整条链:CLI/Pro/Cloud 许可,CI runner 与 dev database 资源,Registry 和日志留存,数据库验证实例,平台 owner 维护,网络与合规接入,以及退出迁移。免费 CLI 加大量自建门禁可能比托管能力更贵;反过来,购买 Pro 也不会自动解决表规模、应用兼容和数据回填责任。
团队长期治理以不变量收尾
数据库变更能力稳定后,团队应持续证明下面这些不变量,而不是统计“本月执行了多少条 migration”:
每个目标 schema 只有一个获准的迁移事实源和执行入口。已进入共享环境的迁移文件保持不可变,任何修复都新增版本。atlas.sum、迁移目录、提交 SHA、CLI 版本和交付制品可以互相追溯。
dev database 与目标方言、scope 和关键版本兼容,并且任务间隔离。PR 不持有目标库写权限;apply 使用短期、最小、可审计身份。每次 apply 前能看到 pending 版本、SQL、风险和目标身份,之后能看到 revision、schema 与业务探针收敛。
危险 DDL 有数据证据、兼容窗口、停止条件和恢复责任人。结构回退与数据恢复分别演练,不能用反向 DDL 冒充数据恢复。漂移不会被下一次 apply 静默吞掉,人工 DDL 必须进入事件与调和流程。
Atlas、数据库主版本、ORM provider 和 CI action 升级都经过双版本 diff 与代表性复演。
职责也要落到具体对象:业务开发者维护期望结构与兼容代码,数据库 owner 审查锁、容量和恢复,平台 owner 维护 CLI、dev database、runner 和凭据链,安全 owner 管理 Cloud 与数据库身份,发布负责人决定批次和停止条件。任何角色都不应同时拥有修改迁移历史、绕过审查和直接写生产 schema 的全部能力。
当一次变更能够从模型或 schema 追到 diff,从 diff 追到迁移文件和哈希,从哈希追到 CI 风险证据,从 apply 追到 revision、真实结构和业务探针,并且失败时知道数据与结构分别怎样恢复,Atlas 才真正成为数据库变更控制面。否则,它只是另一个能更快执行 DDL 的客户端。
