Community寫作與編輯github.com

db-migration-safe

零停機 SQL 遷移安全代理技能:偵測鎖定風險、改寫不安全 DDL、自動生成回退、閘控執行(優先支援 Postgres,封裝 squawk + eugene)。

db-migration-safe 是什麼?

db-migration-safe 是一個專門設計用於安全執行 SQL 資料庫遷移的 AI 代理技能,主要針對 Postgres(強確定性路徑)與 MySQL(混合支援)。它透過 shell out 至外部 lint 工具(squawk、eugene)與線上結構變更工具(gh-ost、pt-osc)來分析遷移檔案,偵測可能導致鎖定或阻斷的危險操作。技能提供完整的「規劃 → 驗證 → 執行」工作流程:PLAN 階段執行靜態分析並產生風險報告與安全改寫建議;VALIDATE 階段確認改寫後的遷移已無錯誤;EXECUTE 階段則以嚴格的鎖定防護與批次回填指導來協助使用者實際套用遷移。安全合約強調 PLAN 與 VALIDATE 永不接觸正式資料庫,EXECUTE 僅在使用者明確要求時才進入。支援的平台包括任何使用 DDL 的專案,特別適用於 ORM 生成的遷移或手寫 SQL,但不建議用於純查詢或擁有自有安全機制的宣告式架構。

相容平台~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 結構變更遷移變得更安全。此技能能偵測遷移中的鎖定/阻塞風險,將不安全的 DDL 改寫為零停機的多步驟遷移,產生回退腳本,並將實際執行流程閘控在「規劃 → 驗證 → 執行」的工作流程中。

Postgres 是強確定性路徑。MySQL 為誠實的混合路徑(參閱 references/mysql-catalog.md)——實際上較弱,並已明確標示。

此技能會 shell out 至外部 lint 工具(squawk, eugene)與 OSC 工具(gh-ost/pt-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 報表查詢,或擁有自有套用步驟的宣告式結構引擎(例如 Atlas)——這些已有自己的安全路徑。

安全合約(請先閱讀)

  • PLAN 和 VALIDATE 絕不觸及使用者的資料庫。它們僅進行分析,可自由執行。
  • eugene trace 會對一個臨時拋棄式 Postgres(或一個可丟棄的複本)執行遷移並預設回滾。它絕不觸碰正式環境。
  • EXECUTE 會對真實資料庫套用真正的 DDL。除非使用者明確要求套用/執行/發布遷移,否則絕不執行 EXECUTE。務必先進行空跑(dry-run),務必包裝在 lock_timeout 中,絕不在交易內執行 CONCURRENTLY。EXECUTE 是引導使用者的程序性指導——沒有自動執行腳本。

關於自動觸發:這是一個單一技能,因此其 frontmatter 會規範整個檔案。PLAN/VALIDATE 可安全地自動觸發,因此保留模型呼叫的啟用狀態。EXECUTE 階段是由上述規則行為上閘控的。如果您更傾向讓整個技能僅由使用者觸發,請在 frontmatter 中加入 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 鎖定 lint 工具;請參閱 references/mysql-catalog.md)。

閱讀合併後的判決:

  1. 發現列表 — 每一項都有 source(squawk/eugene/heuristic)、規則 ID、level(error/warning)、行號、訊息及因應方式。
  2. 解讀 — 透過 references/squawk-rules.md 與 references/eugene-hints.md 將規則 ID 對應為白話解釋。
  3. 資料表大小 — 靜態 lint 工具無法分辨 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]可為 Null 的資料行 → 設定預設值 → 批次回填 → 透過 CHECK 設定 NOT NULL
SET NOT NULLADD CHECK (c IS NOT NULL) NOT VALID → VALIDATE → SET NOT NULL → 刪除 check
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(<table>_<col>_backup_<date>)以及盡力還原的腳本。它絕不發出無聲錯誤的反向操作。
  • 未知的陳述式形式會產生明確的 需要手動回退:<原因> 註解,而非猜測。

VALIDATE

這是閘關。對改寫後的遷移重新執行分析器:

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

僅在零錯誤等級發現(警告可透過 .squawk.toml / -- eugene: ignore 設定)時才繼續。如果仍標記錯誤,則返回安全改寫並循環。如果改寫改變了鎖定模式,請重新執行 trace。

EXECUTE(閘控 — 僅限使用者觸發)

除非使用者明確要求套用遷移,否則不要進入此階段。

在鎖定防護下套用通過驗證的遷移。先進行空跑。

  1. 約束每個鎖定。 將阻塞陳述式包裝起來,使其快速失敗,而不是在長時間交易後排隊等候並阻斷所有流量:
    SET lock_timeout = '5s';
    SET statement_timeout = '0';  -- 或針對特定操作設定一個合理的上限
    
  2. CONCURRENTLY 必須在交易外執行。 不要將 CREATE/DROP INDEX CONCURRENTLY 放在 BEGIN/COMMIT 區塊或交易型遷移執行工具中。在並行建立失敗後,請先刪除殘留的 INVALID 索引再重試。
  3. 兩階段限制條件。 先執行 NOT VALID 步驟,讓它提交,然後在獨立的陳述式中執行 VALIDATE CONSTRAINT。
  4. 批次回填。 在各自獨立的短交易中以有限區塊進行更新,批次之間加入短暫停頓,讓複製與 autovacuum 能跟上:
    -- 重複直到影響 0 行
    UPDATE t SET col = expr
    WHERE id IN (SELECT id FROM t WHERE col IS NULL ORDER BY id LIMIT 5000)
    
  5. 空跑透過 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。

相關技能