数据库迁移Drizzle零停机Claude Code
用 AI 做数据库迁移:零停机 schema 变更工作流
发布 2026-06-21更新 2026-09-20核实 2026-09-20
一句话结论
数据库迁移的风险不在「SQL 写不写得对」,而在**「这条 SQL 在生产上会不会锁表、能不能回滚」**。所以这套工作流的分工是固定的:AI 负责生成迁移文件、列影响面、写回滚方案;人负责审 SQL、拍板执行窗口。
三条不可让步的原则:永远可回滚、大变更分阶段、先兼容后破坏。剩下所有步骤都是这三条的展开。
为什么需要单独一套流程
代码发布错了可以秒回滚;数据库 schema 变更一旦执行,回滚窗口和数据状态是绑在一起的——加了字段又写入数据之后,down migration 就不是无损的了。这就是为什么它不能和代码发布混在一条流水线里跑。
AI 之家 观点:迁移事故里最常见的不是「SQL 语法错」,而是**「语法对、但它在千万行表上锁了 40 秒」**。所以下面的每一步都在回答同一个问题:这条语句在生产上的代价是什么?
适用场景
- 生产数据库需要 schema 变更
- 大表(千万行+)加列 / 改类型 / 加索引
- 需要零停机迁移
- 想让 AI 生成迁移文件 + 回滚方案
迁移原则
- 永远可回滚——每个迁移文件都有对应的 down migration
- 分阶段执行——大变更拆成多步,每步都可独立回滚
- 先兼容后破坏——先让代码兼容新 schema,再删旧字段
- 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 会:
- 查当前 users 表结构
- 生成 Drizzle schema 改动
- 生成 SQL migration 文件
- 生成回滚 SQL
- 检查代码里的
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 字段已删
```
踩坑记录
- PostgreSQL 11+ 加可空字段才是即时——低版本加字段仍会锁表,先升级。
- CONCURRENTLY 索引失败要手动清理——失败后留 invalid index,
DROP INDEX后重建。 - 回填脚本要分批 + sleep——大批量 UPDATE 会撑爆 WAL 和主从延迟。
- NOT VALID + VALIDATE 两步走——直接加 CHECK 会全表扫描锁表。
- 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 全自动部署。
相关阅读
- 方案:用 AI Agent 全自动部署 · AI PR 审查流水线
- 工具卡:Claude Code · Cursor
- 概念:AGENTS.md
来源说明:本文 SQL 示例基于 PostgreSQL 公开行为(可空字段即时添加、
CONCURRENTLY建索引、NOT VALID+VALIDATE两阶段加约束),Drizzle 为迁移工具示例;具体行为请以你所用 PostgreSQL 版本官方文档为准,MySQL / TiDB 等引擎不适用本文的锁表结论。