跳转到内容

数据库迁移

来源: everything-claude-code 技能库

database-migrations 涵盖数据库迁移的最佳实践,包括 Schema 变更、数据迁移、回滚和零停机部署。

  1. 每个变更都是迁移 — 永远不要手动修改生产数据库
  2. 迁移在生产环境只向前 — 回滚使用新的前向迁移
  3. Schema 和数据迁移分开 — 永远不要在同一迁移中混合 DDL 和 DML
  4. 在生产规模数据上测试迁移 — 100 行能工作的迁移可能在 1000 万行时锁表
  5. 迁移一旦部署就不可变 — 永远不要编辑已在生产环境运行过的迁移

在应用任何迁移之前:

  • 迁移有 UP 和 DOWN(或明确标记为不可逆)
  • 大表没有全表锁(使用并发操作)
  • 新列有默认值或可为空(永远不要添加无默认值的 NOT NULL)
  • 索引并发创建(现有表不要内联 CREATE INDEX)
  • 数据回填与 Schema 变更分开
  • 在生产数据副本上测试
  • 记录回滚计划
-- 好:可空列,无锁
ALTER TABLE users ADD COLUMN avatar_url TEXT;
-- 好:带默认值的列(Postgres 11+ 即时完成,无需重写)
ALTER TABLE users ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT true;
-- 不好:无默认值的 NOT NULL(需要全表重写)
ALTER TABLE users ADD COLUMN email VARCHAR(255) NOT NULL;
-- 好:先标记废弃,再删除
-- 步骤 1: 添加新列,迁移数据
ALTER TABLE users ADD COLUMN new_email VARCHAR(255);
UPDATE users SET new_email = email WHERE email IS NOT NULL;
-- 步骤 2: 切换引用
-- (修改代码使用新列)
-- 步骤 3: 删除旧列
ALTER TABLE users DROP COLUMN email;
-- 好:并发创建索引(不锁表)
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
-- 不好:普通创建索引(锁表)
CREATE INDEX idx_users_email ON users(email);
-- 好:使用锁超时避免长时间锁
SET lock_timeout = '1s';
ALTER TABLE orders ADD COLUMN status VARCHAR(50);
-- 好:分批更新大数据
UPDATE orders SET status = 'pending'
WHERE id BETWEEN 1 AND 100000;
-- 重复直到完成
-- 检查表大小
SELECT
schemaname,
relname,
n_live_tup as row_count,
pg_size_pretty(pg_total_relation_size(relid)) as size
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC
LIMIT 10;
-- 策略 1:分批处理
DO $$
DECLARE
batch_size INTEGER := 10000;
total_updated INTEGER := 0;
BEGIN
LOOP
UPDATE users
SET processed = true
WHERE id IN (
SELECT id FROM users
WHERE processed = false
ORDER BY id
LIMIT batch_size
);
GET DIAGNOSTICS total_updated = ROW_COUNT;
IF total_updated < batch_size THEN
EXIT;
END IF;
PERFORM pg_sleep(0.1); -- 让其他操作有机会
END LOOP;
END $$;
-- 迁移后验证
-- 1. 检查空值
SELECT COUNT(*) FROM users WHERE email IS NULL AND email_new IS NOT NULL;
-- 2. 检查一致性
SELECT COUNT(*) FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE u.id IS NULL;
-- 3. 检查约束
ALTER TABLE users ADD CONSTRAINT email_unique UNIQUE (email_new);
# 永远不要编辑已存在的迁移
# 正确:创建新的迁移来撤销
# 2024_01_15_001_add_users_table.py (原迁移)
def upgrade():
op.create_table('users',
op.Column('id', sa.Integer()),
op.Column('email', sa.String(255)),
)
# 2024_01_20_001_rollback_users_table.py (新迁移撤销)
def upgrade():
op.drop_table('users')
-- 1. 在新表上工作
CREATE TABLE users_new ( ... );
-- 2. 同步数据
INSERT INTO users_new SELECT * FROM users;
-- 3. 切换
ALTER TABLE users RENAME TO users_old;
ALTER TABLE users_new RENAME TO users;
-- 4. 验证
-- 5. 删除旧表
DROP TABLE users_old;
✅ 正确做法:
1. 先添加可空列
2. 迁移数据
3. 修改代码使用新列
4. 添加 NOT NULL 约束
5. 删除旧列
❌ 错误做法:
1. 直接添加 NOT NULL 列
2. 在大表上操作
3. 不测试
✅ 正确做法:
1. 使用 CONCURRENTLY 关键字
2. 设置锁超时
3. 在低峰期执行
4. 验证索引使用
❌ 错误做法:
1. 普通 CREATE INDEX
2. 在高峰期间操作
3. 不检查锁等待
✅ 正确做法:
1. 预估数据量
2. 分批处理
3. 验证数据完整性
4. 准备回滚
❌ 错误做法:
1. 一次性更新全表
2. 不检查约束
3. 忽略外键关系
Terminal window
# Alembic (Python)
alembic revision --autogenerate -m "add column"
alembic upgrade head
alembic downgrade -1
# Prisma
prisma migrate dev
prisma migrate deploy
prisma migrate reset
# Django
python manage.py makemigrations
python manage.py migrate
# golang-migrate
migrate -path=migrations -database postgres://localhost/db up
# SQL
psql -d dbname -f migration.sql

手动修改

错误:直接修改生产数据库 正确:使用迁移脚本

大表锁表

错误:普通 ALTER 操作 正确:使用 CONCURRENTLY

混合迁移

错误:DDL 和 DML 在一起 正确:分开执行

不测试

错误:在生产直接运行 正确:在备份数据上测试

何时使用

  • 创建或修改表
  • 添加/删除列或索引
  • 数据迁移
  • 零停机部署

何时不用

  • 本地开发(可用 reset)
  • 测试环境
  • 一次性查询

关键要点

  • 迁移不可变
  • 先测试再部署
  • 备好回滚
  • 分批处理大数据

常见错误

  • 手动修改
  • 大表锁表
  • 不测试
  • 无回滚计划
ORM命令
Prismaprisma migrate dev
Djangopython manage.py makemigrations
Alembicalembic revision --autogenerate
Kyselykysely-codegen
TypeORMtypeorm migration:generate
  1. 蓝绿部署 - 切换表名
  2. 影子表 - 在新表操作后切换
  3. 在线迁移 - 使用触发器同步
  4. 分阶段发布 - 功能开关控制
  • 始终有回滚方案
  • 小步迭代
  • 在非高峰执行
  • 监控锁等待
  • 准备紧急回滚
技能关系
postgres-patternsPostgreSQL 模式
python-testing测试驱动开发
code-review代码审查
  1. 选择工具 - Prisma/Alembic/Django
  2. 配置迁移目录 - 设置数据库连接
  3. 创建迁移 - makemigrations
  4. 测试 - 在备份数据上运行
  5. 部署 - 在低峰期应用

官方原文: database-migrations SKILL.md


💡 提示:数据库迁移就像做手术,务必做好准备再动手。