跳到主内容
数据库迁移Drizzle零停机Claude Code

用 AI 做数据库迁移:零停机 schema 变更工作流

发布 2026-06-21更新 2026-09-20核实 2026-09-20
用 AI 做数据库迁移:零停机 schema 变更工作流

一句话结论

数据库迁移的风险不在「SQL 写不写得对」,而在**「这条 SQL 在生产上会不会锁表、能不能回滚」**。所以这套工作流的分工是固定的:AI 负责生成迁移文件、列影响面、写回滚方案;人负责审 SQL、拍板执行窗口。

三条不可让步的原则:永远可回滚大变更分阶段先兼容后破坏。剩下所有步骤都是这三条的展开。

为什么需要单独一套流程

代码发布错了可以秒回滚;数据库 schema 变更一旦执行,回滚窗口和数据状态是绑在一起的——加了字段又写入数据之后,down migration 就不是无损的了。这就是为什么它不能和代码发布混在一条流水线里跑。

AI 之家 观点:迁移事故里最常见的不是「SQL 语法错」,而是**「语法对、但它在千万行表上锁了 40 秒」**。所以下面的每一步都在回答同一个问题:这条语句在生产上的代价是什么?

适用场景

  • 生产数据库需要 schema 变更
  • 大表(千万行+)加列 / 改类型 / 加索引
  • 需要零停机迁移
  • 想让 AI 生成迁移文件 + 回滚方案

迁移原则

  1. 永远可回滚——每个迁移文件都有对应的 down migration
  2. 分阶段执行——大变更拆成多步,每步都可独立回滚
  3. 先兼容后破坏——先让代码兼容新 schema,再删旧字段
  4. AI 生成 + 人工审查——AI 写迁移,人审 SQL

工作流

需求:给 users 表加 phone 字段
  → Step 1: Claude 生成迁移文件(含 up + down)
  → Step 2: Claude 检查兼容性(是否破坏现有代码)
  → Step 3: 在 shadow DB 测试迁移
  → Step 4: 生产分阶段执行
  → Step 5: 验证 + 清理

Step 1: AI 生成迁移

用 Claude Code 连数据库(通过 MCP),描述需求:

给 users 表加一个 phone 字段,varchar(20),可空,加唯一索引。
用 Drizzle migration 格式生成,含 up 和 down。
检查现有代码是否有依赖。

Claude 会:

  1. 查当前 users 表结构
  2. 生成 Drizzle schema 改动
  3. 生成 SQL migration 文件
  4. 生成回滚 SQL
  5. 检查代码里的 select *insert 是否受影响

生成的文件:

-- migrations/0024_add_user_phone.sql

-- UP
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
CREATE UNIQUE INDEX idx_users_phone ON users(phone) WHERE phone IS NOT NULL;

-- DOWN
DROP INDEX IF EXISTS idx_users_phone;
ALTER TABLE users DROP COLUMN IF EXISTS phone;

Step 2: 兼容性检查

检查这次迁移会影响哪些代码:
1. 哪些 INSERT 语句需要加 phone 字段
2. 哪些 SELECT * 会导致返回字段变化
3. 哪些 API 响应 schema 会变

Claude 输出影响清单,你确认后再执行。

Step 3: 高危操作安全模式

大表加列(千万行+)

-- ❌ 错误:直接加(锁表)
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- ✅ 正确:分阶段
-- Phase 1: 加可空字段(不锁表,PostgreSQL 11+)
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- Phase 2: 回填数据(分批,不锁表)
-- Claude 生成批处理脚本
DO $$
DECLARE
  batch_size INT := 10000;
  offset_val INT := 0;
BEGIN
  LOOP
    UPDATE users SET phone = '' WHERE id IN (
      SELECT id FROM users WHERE phone IS NULL LIMIT batch_size
    );
    GET DIAGNOSTICS batch_size = ROW_COUNT;
    EXIT WHEN batch_size = 0;
    PERFORM pg_sleep(0.1);  -- 给主从复制留时间
  END LOOP;
END $$;

-- Phase 3: 加约束(先检查再加)
ALTER TABLE users ADD CONSTRAINT chk_phone CHECK (phone ~ '^\+?[0-9]{6,20}$') NOT VALID;
ALTER TABLE users VALIDATE CONSTRAINT chk_phone;

改字段类型

-- ❌ 错误:直接改(锁表 + 重写全表)
ALTER TABLE users ALTER COLUMN phone TYPE BIGINT USING phone::BIGINT;

-- ✅ 正确:新字段 + 回填 + 切换 + 删旧
-- Phase 1: 加新字段
ALTER TABLE users ADD COLUMN phone_int BIGINT;

-- Phase 2: 双写(代码同时写旧和新)
-- Claude 生成代码改动:INSERT/UPDATE 同时写 phone 和 phone_int

-- Phase 3: 回填
UPDATE users SET phone_int = phone::BIGINT WHERE phone_int IS NULL AND phone ~ '^[0-9]+$';

-- Phase 4: 验证数据一致
SELECT COUNT(*) FROM users WHERE phone IS NOT NULL AND phone_int IS NULL;
-- 必须为 0

-- Phase 5: 代码切到读 phone_int

-- Phase 6: 删旧字段(等一个发布周期后)
ALTER TABLE users DROP COLUMN phone;
ALTER TABLE users RENAME COLUMN phone_int TO phone;

加索引

-- ❌ 错误:直接加(锁表写操作)
CREATE INDEX idx_users_email ON users(email);

-- ✅ 正确:CONCURRENTLY(不锁表,但慢)
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
-- 注意:CONCURRENTLY 不能在事务里跑

Step 4: 执行 + 监控

# 1. 在 shadow DB 测试
psql $SHADOW_DB -f migrations/0024_add_user_phone.sql

# 2. 生产执行(维护窗口)
psql $PROD_DB -f migrations/0024_add_user_phone.sql

# 3. 监控(Claude 帮你写监控脚本)
watch -n 5 'psql $PROD_DB -c "
  SELECT
    count(*) AS total,
    count(phone) AS with_phone,
    count(*) - count(phone) AS without_phone
  FROM users
"'

Step 5: 回滚方案

每个迁移执行前,Claude 生成回滚 checklist:

## 回滚步骤(如需)

1. 确认 down migration 安全:
   ```sql
   SELECT count(*) FROM users WHERE phone IS NOT NULL;
   -- 如果 > 0,回滚会丢数据,确认是否可接受
   ```

2. 执行回滚:
   ```bash
   psql $PROD_DB -f migrations/0024_add_user_phone_down.sql
   ```

3. 代码回滚到上一个版本:
   ```bash
   git revert <merge-commit>
   ```

4. 验证:
   ```sql
   \d users  -- 确认 phone 字段已删
   ```

踩坑记录

  1. PostgreSQL 11+ 加可空字段才是即时——低版本加字段仍会锁表,先升级。
  2. CONCURRENTLY 索引失败要手动清理——失败后留 invalid index,DROP INDEX 后重建。
  3. 回填脚本要分批 + sleep——大批量 UPDATE 会撑爆 WAL 和主从延迟。
  4. NOT VALID + VALIDATE 两步走——直接加 CHECK 会全表扫描锁表。
  5. Claude 生成 SQL 必须 review——AI 偶尔会忘加 WHERE 条件,删数据操作尤其要审。

执行前 checklist

执行生产迁移前,逐项打勾,任一为否就不发:

  • 迁移文件同时包含 up 与 down,且 down 已在本机或 shadow DB 实跑过一次
  • 已确认目标 PostgreSQL 版本(<11 加字段会锁表,见踩坑 1)
  • 大表变更已拆成「加可空字段 → 分批回填 → 加约束 / 切读」多阶段,每阶段可独立停
  • 已评估「down migration 是否会丢数据」,若会丢,已确认业务可接受或改用新字段方案
  • 已在 shadow DB 完整跑过一遍,耗时已记录
  • 已确认维护窗口与主从延迟监控,回填脚本带 pg_sleep
  • 代码侧已完成兼容(双写 / 兼容新旧字段),代码先于 schema 上线
  • 回滚负责人与回滚命令已写在变更单里,而不是留在聊天记录里

什么时候别让 AI 碰

  • 删表 / 删列 / truncate —— 生成即可,执行必须人工;
  • 主从延迟已经很高的时候跑回填 —— 分批再小也会加剧;
  • 没有 shadow DB 就直接上生产 —— 至少用一份最近的备份恢复到临时实例;
  • 迁移与代码发布绑在同一条流水线 —— 两者回滚粒度不同,必须分开,参见 用 AI Agent 全自动部署

相关阅读

来源说明:本文 SQL 示例基于 PostgreSQL 公开行为(可空字段即时添加、CONCURRENTLY 建索引、NOT VALID + VALIDATE 两阶段加约束),Drizzle 为迁移工具示例;具体行为请以你所用 PostgreSQL 版本官方文档为准,MySQL / TiDB 等引擎不适用本文的锁表结论。

相关工具