Community写作与编辑github.com

db-migration-safe

零停机 SQL 迁移安全 Agent 技能:检测锁/阻塞风险、重写不安全 DDL、自动生成回滚、执行门控(优先支持 Postgres,集成 squawk 和 eugene)。

db-migration-safe 是什么?

db-migration-safe 是一个专注于数据库 SQL 迁移安全的 Agent 技能。它能够检测迁移脚本中的锁定和阻塞风险,自动将不安全的 DDL 重写为多步骤的零停机迁移方案,并生成对应的回滚脚本。该技能通过 plan → validate → execute 的三阶段门控工作流程来保证安全性。目前主要支持 Postgres(确定性最强),对 MySQL 也提供了一部分基于 InnoDB Online DDL 启发式分析的有限支持。技能底层调用了 squawk、eugene 等外部静态分析工具以及 gh-ost/pt-osc 等在线 Schema 变更工具,但本身不打包这些工具,需要用户按指南手动安装。静态分析阶段无需连接数据库即可运行,而 trace 和 EXECUTE 阶段则需要 Postgres 实例。该技能特别适用于包含 CREATE INDEX、ADD/DROP COLUMN、约束变更、重命名等 DDL 操作的迁移脚本,但不适用于纯 SELECT/DML 查询或像 Atlas 这样的声明式 Schema 引擎。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add alexbobkovv/db-migration-safe

Installed? Explore more 写作与编辑 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

db-migration-safe 是做什么的?

让 SQL Schema 迁移变得安全。该技能能够检测迁移中的锁/阻塞风险,将不安全的 DDL 重写为零停机多步骤迁移,生成回滚脚本,并在计划 → 验证 → 执行的工作流后进行实际执行门控。

Postgres 是强确定性的路径。MySQL 是一种诚实的混合方案(参见 references/mysql-catalog.md)—— 实质较弱,并已明确标注。

该技能外部调用了 squawk、eugene 等检查工具以及 gh-ost/pt-osc 等 OSC 工具。它不附带这些工具。如果缺少它们,请按照 references/tool-setup.md 进行安装。静态分析(squawk + eugene lint)不需要数据库;eugene trace 和 EXECUTE 则需要一个 Postgres。

何时使用 / 何时不使用

使用于任何 DDL:ALTER TABLE、CREATE INDEX、ADD/DROP COLUMN、类型变更、约束(NOT NULL、CHECK、UNIQUE、外键)、重命名和数据回填 —— 无论是手写 SQL 还是 ORM 生成的。不要用于纯 SELECT/DML 报告查询,或用于拥有自己应用步骤的声明式 Schema 引擎(例如 Atlas)—— 它们有自己的安全路径。

安全契约(请先阅读)

  • PLAN 和 VALIDATE 永远不会接触用户的数据库。它们只是分析阶段,可以自由运行。
  • eugene trace 针对一个临时的、用完即弃的 Postgres(或可丢弃的克隆)运行迁移,默认会回滚。它永远不会接触生产环境。
  • EXECUTE 将真实的 DDL 应用到真实数据库。除非用户明确要求执行/运行/部署迁移,否则永远不要运行 EXECUTE。 始终先进行 dry-run,始终包裹在 lock_timeout 中,永远不要在事务内运行 CONCURRENTLY。EXECUTE 是用户驱动的程序化指导——没有自动执行脚本。

关于自动调用的说明:这是一个单一技能,所以其前置元数据控制整个文件。PLAN/VALIDATE 是安全的自动触发操作,因此模型调用保持启用。EXECUTE 阶段通过上述规则行为上进行门控。如果您希望整个技能仅由用户触发,请在前置元数据中添加 disable-model-invocation: true——这样即使分析阶段也需要显式调用。


PLAN

对迁移文件运行静态分析器。不需要数据库。

python3 scripts/analyze.py path/to/migration.sql --dialect postgres
# 添加 --pg-version 13.0 以限制版本特定规则
# 添加 --json 以输出机器可读结果(CI 中使用)

analyze.py 运行 squawk 和 eugene lint,将两者规范化为一个综合裁决,如果存在任何错误级别的发现,则以非零退出码退出(因此它也可作为 CI 门控)。对于 --dialect mysql,它会应用内置的 InnoDB Online DDL 启发式分析替代(目前没有免费的静态 MySQL 锁检查工具;参见 references/mysql-catalog.md)。

阅读合并后的裁决:

  1. 发现项 —— 每项都有一个 source(squawk/eugene/heuristic)、规则 ID、level(error/warning)、行号、消息和解决方法。
  2. 解释 —— 通过 references/squawk-rules.md 和 references/eugene-hints.md 将规则 ID 映射为通俗含义。
  3. 表大小 —— 静态检查工具无法区分 100 行和 10 万行。在判断一个“慢”操作是否真的是问题之前,使用 scripts/table_size.sql 探测实际大小(需要数据库访问):
    psql "$DATABASE_URL" -v tablename=public.orders -f scripts/table_size.sql
    
  4. 升级到 trace —— 当裁决涉及可能的表/索引重写(E5、E10)、没有覆盖索引的外键(E15)或任何实际持续时间取决于行数的锁时。trace 会观察实际的锁模式及其持续时长:
    python3 scripts/trace.py path/to/migration.sql            # 临时服务器
    python3 scripts/trace.py path/to/migration.sql --host db.staging --port 5432 \
        --user app --database app                              # 可丢弃的克隆
    

然后为用户生成三个产物:

  • 一个风险报告(裁决,通俗语言),
  • 一个安全重写(下一节),以及
  • 一个自动生成的回滚(scripts/gen_rollback.py)。

安全重写

对于每个错误/警告发现项,将不安全的语句替换为目录中收录的零停机重写方案。完整的 11 种操作 → 重写表见 references/postgres-catalog.md(MySQL:references/mysql-catalog.md)。几乎所有重写背后的两个 Postgres 原语:

  • CONCURRENTLY —— 在无 ACCESS EXCLUSIVE 写锁的情况下构建/删除索引(必须在事务外运行)。
  • NOT VALID → VALIDATE CONSTRAINT —— 分两阶段添加约束,使昂贵的全表验证使用较弱的 SHARE UPDATE EXCLUSIVE 锁,而不是阻塞写入。

快速参考(具体的多步骤 SQL 及 PG 版本差异详见目录):

不安全安全重写
CREATE INDEXCREATE INDEX CONCURRENTLY(在事务外)
ADD COLUMN ... NOT NULL [DEFAULT]可空列 → 设置默认值 → 批量回填 → 通过 CHECK 实现 NOT NULL
SET NOT NULLADD CHECK (c IS NOT NULL) NOT VALID → VALIDATE → SET NOT NULL → 删除检查
ADD FOREIGN KEY... NOT VALID → VALIDATE CONSTRAINT
ADD CHECK... CHECK (...) NOT VALID → VALIDATE CONSTRAINT
ADD UNIQUECREATE UNIQUE INDEX CONCURRENTLY → ADD CONSTRAINT ... USING INDEX
ALTER COLUMN ... TYPE扩展-收缩(新列 → 双写 → 回填 → 交换 → 删除)
重命名列/表扩展-收缩,或兼容性 VIEW
回填 UPDATE短事务中的有界批次,带节流

将重写后的语句写入新文件(例如 migration.safe.sql),以便可以重新验证。

回滚

从(重写后的)DDL 生成反向迁移:

python3 scripts/gen_rollback.py migration.safe.sql > migration.rollback.sql

gen_rollback.py 为每个可识别的目录操作发出逆操作(ADD COLUMN→DROP COLUMN, CREATE INDEX CONCURRENTLY→DROP INDEX CONCURRENTLY, …)。检查输出:

  • 不可逆操作(DROP COLUMN、DROP TABLE、类型缩小)无法仅从 DDL 恢复。生成器会标记它们,并发出备份表 SQL(正向迁移必须先运行该 SQL:<table>_<col>_backup_<date>)以及尽力而为的恢复。它绝不会发出一个静默错误的逆操作。
  • 未知语句形状会产生显式的 manual rollback required: <reason> 注释,而不是猜测。

VALIDATE

这是门控步骤。在重写后的迁移上重新运行分析器:

python3 scripts/analyze.py migration.safe.sql --dialect postgres

仅当它通过零错误级别发现项时才可以继续(警告可通过 .squawk.toml / -- eugene: ignore 配置)。如果仍然标记错误,请返回到安全重写阶段并循环。如果重写改变了获取的锁,请重新运行 trace。

EXECUTE(门控 —— 仅用户触发)

除非用户明确要求执行迁移,否则不要进入此阶段。

使用锁定防护措施执行已验证的迁移。先进行 dry-run。

  1. 限制每个锁。 包裹阻塞语句,使其快速失败,而不是在长事务后排队并阻塞所有流量:
    SET lock_timeout = '5s';
    SET statement_timeout = '0';  -- 或针对特定操作设置合理上限
    
  2. CONCURRENTLY 在事务外运行。 不要将 CREATE/DROP INDEX CONCURRENTLY 放在 BEGIN/COMMIT 块或事务性迁移运行器中。并发构建失败后,在重试前删除残留的 INVALID 索引。
  3. 两阶段约束。 先运行 NOT VALID 步骤,提交,然后在单独的语句中执行 VALIDATE CONSTRAINT。
  4. 分批回填。 在独立的短事务中以有界块进行更新,批次之间短暂休眠,以便复制和自动清理跟上:
    -- 重复直到影响 0 行
    UPDATE t SET col = expr
    WHERE id IN (SELECT id FROM t WHERE col IS NULL ORDER BY id LIMIT 5000)
    
  5. 最后对整个序列进行 dry-run:通过 scripts/trace.py 对临时/克隆环境运行,确认 passed_all_checks,然后应用到目标。

对于 MySQL,强制使用 ALGORITHM=…, LOCK=…,这样服务器会在静默回退到 COPY 重建时报错,并将大重写委托给 gh-ost/pt-osc——参见 references/mysql-catalog.md。


参考索引

  • references/postgres-catalog.md —— 11 种不安全操作 → 安全重写(核心知识)。
  • references/mysql-catalog.md —— InnoDB Online DDL 矩阵 + OSC 委托(较弱路径)。
  • references/squawk-rules.md —— squawk 规则 ID 及其捕获内容。
  • references/eugene-hints.md —— eugene E1–E15 / W12–W14 及其解决方法。
  • references/tool-setup.md —— 安装 squawk / eugene / gh-ost;临时 Postgres。

脚本

  • scripts/analyze.py —— 编排 squawk + eugene lint(或 MySQL 启发式)→ 一个综合裁决。存在错误级别发现时以非零退出。
  • scripts/trace.py —— 运行 eugene trace(临时服务器或克隆)→ 真实锁报告。
  • scripts/gen_rollback.py —— 生成反向迁移;标记不可逆操作。
  • scripts/table_size.sql —— pg_class.reltuples 大小探测。

所有脚本仅依赖 Python 3 标准库 —— 无需 pip install。

相关技能