跳到主内容
AIHO 2026 全新改版上线

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

适用场景

  • 生产数据库需要 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,回滚会丢数据,确认是否可接受
  1. 执行回滚:
    psql $PROD_DB -f migrations/0024_add_user_phone_down.sql
    
  2. 代码回滚到上一个版本:
    git revert <merge-commit>
    
  3. 验证:
    \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 条件,删数据操作尤其要审。