prisma-migration-assistant
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePrisma Migration Assistant
Prisma迁移助手
Plan and execute safe Prisma migrations with confidence.
放心地规划并执行安全的Prisma迁移。
Migration Planning Workflow
迁移规划流程
typescript
// 1. Update schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
// NEW: Split name into firstName and lastName
firstName String?
lastName String?
// OLD: name String // Will remove this
createdAt DateTime @default(now())
}
// 2. Create migration
// npx prisma migrate dev --name split_user_name --create-only
// 3. Review generated SQL
// 4. Add data migration
// 5. Test migration
// 6. Apply to productiontypescript
// 1. 更新schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
// 新增:将name拆分为firstName和lastName
firstName String?
lastName String?
// 旧字段:name String // 将会删除此字段
createdAt DateTime @default(now())
}
// 2. 创建迁移
// npx prisma migrate dev --name split_user_name --create-only
// 3. 审阅生成的SQL
// 4. 添加数据迁移
// 5. 测试迁移
// 6. 应用到生产环境Migration Types
迁移类型
1. Additive Migration (Safe)
1. 增量迁移(安全)
prisma
// Adding new optional field - safe!
model Product {
id Int @id @default(autoincrement())
name String
description String?
price Float
newField String? // NEW - optional, no backfill needed
}bash
undefinedprisma
// 添加新的可选字段 - 安全!
model Product {
id Int @id @default(autoincrement())
name String
description String?
price Float
newField String? // 新增 - 可选,无需回填数据
}bash
undefinedGenerate migration
生成迁移
npx prisma migrate dev --name add_product_new_field
npx prisma migrate dev --name add_product_new_field
SQL generated:
生成的SQL:
ALTER TABLE "Product" ADD COLUMN "newField" TEXT;
ALTER TABLE "Product" ADD COLUMN "newField" TEXT;
undefinedundefined2. Column Rename (Needs Data Copy)
2. 列重命名(需复制数据)
prisma
model User {
id Int @id @default(autoincrement())
emailAddr String @unique // Renamed from 'email'
}sql
-- migrations/20240115_rename_email/migration.sql
-- Step 1: Add new column
ALTER TABLE "User" ADD COLUMN "emailAddr" TEXT;
-- Step 2: Copy data
UPDATE "User" SET "emailAddr" = "email";
-- Step 3: Make new column required
ALTER TABLE "User" ALTER COLUMN "emailAddr" SET NOT NULL;
-- Step 4: Add unique constraint
CREATE UNIQUE INDEX "User_emailAddr_key" ON "User"("emailAddr");
-- Step 5: Drop old column
ALTER TABLE "User" DROP COLUMN "email";prisma
model User {
id Int @id @default(autoincrement())
emailAddr String @unique // 从'email'重命名而来
}sql
-- migrations/20240115_rename_email/migration.sql
-- 步骤1:添加新列
ALTER TABLE "User" ADD COLUMN "emailAddr" TEXT;
-- 步骤2:复制数据
UPDATE "User" SET "emailAddr" = "email";
-- 步骤3:设置新列为必填
ALTER TABLE "User" ALTER COLUMN "emailAddr" SET NOT NULL;
-- 步骤4:添加唯一约束
CREATE UNIQUE INDEX "User_emailAddr_key" ON "User"("emailAddr");
-- 步骤5:删除旧列
ALTER TABLE "User" DROP COLUMN "email";3. Data Transformation (Complex)
3. 数据转换(复杂)
prisma
// Before: Single name field
// After: First and last name
model User {
id Int @id @default(autoincrement())
firstName String
lastName String
// name String // Removed
}sql
-- migrations/20240115_split_name/migration.sql
-- Step 1: Add new columns
ALTER TABLE "User" ADD COLUMN "firstName" TEXT;
ALTER TABLE "User" ADD COLUMN "lastName" TEXT;
-- Step 2: Data migration (split name)
-- PostgreSQL
UPDATE "User"
SET
"firstName" = SPLIT_PART("name", ' ', 1),
"lastName" = CASE
WHEN array_length(string_to_array("name", ' '), 1) > 1
THEN array_to_string((string_to_array("name", ' '))[2:], ' ')
ELSE ''
END
WHERE "name" IS NOT NULL;
-- Step 3: Handle NULL values
UPDATE "User"
SET
"firstName" = COALESCE("firstName", ''),
"lastName" = COALESCE("lastName", '');
-- Step 4: Make columns required
ALTER TABLE "User" ALTER COLUMN "firstName" SET NOT NULL;
ALTER TABLE "User" ALTER COLUMN "lastName" SET NOT NULL;
-- Step 5: Drop old column
ALTER TABLE "User" DROP COLUMN "name";prisma
// 之前:单个name字段
// 之后:名字和姓氏字段
model User {
id Int @id @default(autoincrement())
firstName String
lastName String
// name String // 已删除
}sql
-- migrations/20240115_split_name/migration.sql
-- 步骤1:添加新列
ALTER TABLE "User" ADD COLUMN "firstName" TEXT;
ALTER TABLE "User" ADD COLUMN "lastName" TEXT;
-- 步骤2:数据迁移(拆分name)
-- PostgreSQL
UPDATE "User"
SET
"firstName" = SPLIT_PART("name", ' ', 1),
"lastName" = CASE
WHEN array_length(string_to_array("name", ' '), 1) > 1
THEN array_to_string((string_to_array("name", ' '))[2:], ' ')
ELSE ''
END
WHERE "name" IS NOT NULL;
-- 步骤3:处理NULL值
UPDATE "User"
SET
"firstName" = COALESCE("firstName", ''),
"lastName" = COALESCE("lastName", '');
-- 步骤4:设置列为必填
ALTER TABLE "User" ALTER COLUMN "firstName" SET NOT NULL;
ALTER TABLE "User" ALTER COLUMN "lastName" SET NOT NULL;
-- 步骤5:删除旧列
ALTER TABLE "User" DROP COLUMN "name";4. Type Change (Risky)
4. 类型变更(有风险)
prisma
model Product {
id Int @id @default(autoincrement())
price Decimal @db.Decimal(10, 2) // Changed from Float
}sql
-- migrations/20240115_price_to_decimal/migration.sql
-- Step 1: Add new column with correct type
ALTER TABLE "Product" ADD COLUMN "price_new" DECIMAL(10,2);
-- Step 2: Copy and convert data
UPDATE "Product"
SET "price_new" = CAST("price" AS DECIMAL(10,2));
-- Step 3: Drop old column
ALTER TABLE "Product" DROP COLUMN "price";
-- Step 4: Rename new column
ALTER TABLE "Product" RENAME COLUMN "price_new" TO "price";
-- Step 5: Make NOT NULL if required
ALTER TABLE "Product" ALTER COLUMN "price" SET NOT NULL;prisma
model Product {
id Int @id @default(autoincrement())
price Decimal @db.Decimal(10, 2) // 从Float类型修改而来
}sql
-- migrations/20240115_price_to_decimal/migration.sql
-- 步骤1:添加类型正确的新列
ALTER TABLE "Product" ADD COLUMN "price_new" DECIMAL(10,2);
-- 步骤2:复制并转换数据
UPDATE "Product"
SET "price_new" = CAST("price" AS DECIMAL(10,2));
-- 步骤3:删除旧列
ALTER TABLE "Product" DROP COLUMN "price";
-- 步骤4:重命名新列
ALTER TABLE "Product" RENAME COLUMN "price_new" TO "price";
-- 步骤5:若需要则设置为必填
ALTER TABLE "Product" ALTER COLUMN "price" SET NOT NULL;Migration Sequencing
迁移排序
markdown
undefinedmarkdown
undefinedMigration Sequence: Add User Roles
迁移序列:添加用户角色
Phase 1: Additive (Week 1)
阶段1:增量变更(第1周)
- Add optional field
role - Deploy application code that handles NULL roles
- Backfill existing users with default role
- 添加可选的字段
role - 部署可处理NULL角色的应用代码
- 用默认角色回填现有用户数据
Phase 2: Enforcement (Week 2)
阶段2:强制约束(第2周)
- Make field required
role - Deploy code that requires role on creation
- Add database constraint
- 将字段设置为必填
role - 部署创建用户时必须指定角色的代码
- 添加数据库约束
Phase 3: Cleanup (Week 3)
阶段3:清理(第3周)
- Remove old permission checking code
- Verify all users have roles
undefined- 删除旧的权限检查代码
- 验证所有用户都已分配角色
undefinedBackfill Strategies
回填策略
Small Table (< 10k rows)
小表(<1万行)
typescript
// scripts/backfill-user-roles.ts
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function backfillUserRoles() {
const usersWithoutRoles = await prisma.user.findMany({
where: { role: null },
});
console.log(`Backfilling ${usersWithoutRoles.length} users...`);
// Single transaction for small dataset
await prisma.$transaction(
usersWithoutRoles.map((user) =>
prisma.user.update({
where: { id: user.id },
data: { role: "USER" }, // Default role
})
)
);
console.log("✅ Backfill complete");
}
backfillUserRoles();typescript
// scripts/backfill-user-roles.ts
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function backfillUserRoles() {
const usersWithoutRoles = await prisma.user.findMany({
where: { role: null },
});
console.log(`正在回填 ${usersWithoutRoles.length} 个用户...`);
// 针对小型数据集使用单事务
await prisma.$transaction(
usersWithoutRoles.map((user) =>
prisma.user.update({
where: { id: user.id },
data: { role: "USER" }, // 默认角色
})
)
);
console.log("✅ 回填完成");
}
backfillUserRoles();Large Table (> 10k rows)
大表(>1万行)
typescript
// scripts/backfill-large-table.ts
async function backfillBatched() {
const batchSize = 1000;
let processed = 0;
let hasMore = true;
while (hasMore) {
const batch = await prisma.user.findMany({
where: { role: null },
take: batchSize,
select: { id: true },
});
if (batch.length === 0) {
hasMore = false;
break;
}
// Process batch
await prisma.$transaction(
batch.map((user) =>
prisma.user.update({
where: { id: user.id },
data: { role: "USER" },
})
)
);
processed += batch.length;
console.log(`Processed ${processed} users...`);
// Rate limiting
await new Promise((resolve) => setTimeout(resolve, 100));
}
console.log(`✅ Backfilled ${processed} users`);
}typescript
// scripts/backfill-large-table.ts
async function backfillBatched() {
const batchSize = 1000;
let processed = 0;
let hasMore = true;
while (hasMore) {
const batch = await prisma.user.findMany({
where: { role: null },
take: batchSize,
select: { id: true },
});
if (batch.length === 0) {
hasMore = false;
break;
}
// 处理批次
await prisma.$transaction(
batch.map((user) =>
prisma.user.update({
where: { id: user.id },
data: { role: "USER" },
})
)
);
processed += batch.length;
console.log(`已处理 ${processed} 个用户...`);
// 速率限制
await new Promise((resolve) => setTimeout(resolve, 100));
}
console.log(`✅ 已回填 ${processed} 个用户`);
}Rollback Guidance
回滚指南
sql
-- migrations/20240115_add_role/rollback.sql
-- Rollback Step 1: Add back old structure (if needed)
ALTER TABLE "User" DROP COLUMN "role";
-- Rollback Step 2: Restore old logic
-- (Deploy previous application version)
-- Note: Data loss consideration
-- If you backfilled data, document what was lostsql
-- migrations/20240115_add_role/rollback.sql
-- 回滚步骤1:恢复旧结构(若需要)
ALTER TABLE "User" DROP COLUMN "role";
-- 回滚步骤2:恢复旧逻辑
-- (部署之前的应用版本)
-- 注意:数据丢失风险
-- 若已执行数据回填,请记录可能丢失的数据Migration Testing
迁移测试
typescript
// tests/migrations/split-name.test.ts
import { PrismaClient } from "@prisma/client";
import { execSync } from "child_process";
describe("Split name migration", () => {
let prisma: PrismaClient;
beforeAll(async () => {
// Setup test database
execSync("npx prisma migrate deploy", {
env: { DATABASE_URL: process.env.TEST_DATABASE_URL },
});
prisma = new PrismaClient();
});
it("should split name correctly", async () => {
// Create user with old schema
await prisma.$executeRaw`
INSERT INTO "User" (name) VALUES ('John Doe')
`;
// Run migration
execSync("npx prisma migrate deploy");
// Verify split
const user = await prisma.user.findFirst();
expect(user?.firstName).toBe("John");
expect(user?.lastName).toBe("Doe");
});
it("should handle single name", async () => {
await prisma.$executeRaw`
INSERT INTO "User" (name) VALUES ('Madonna')
`;
execSync("npx prisma migrate deploy");
const user = await prisma.user.findFirst({
where: { firstName: "Madonna" },
});
expect(user?.lastName).toBe("");
});
});typescript
// tests/migrations/split-name.test.ts
import { PrismaClient } from "@prisma/client";
import { execSync } from "child_process";
describe("拆分name字段迁移", () => {
let prisma: PrismaClient;
beforeAll(async () => {
// 搭建测试数据库
execSync("npx prisma migrate deploy", {
env: { DATABASE_URL: process.env.TEST_DATABASE_URL },
});
prisma = new PrismaClient();
});
it("应正确拆分name字段", async () => {
// 用旧schema创建用户
await prisma.$executeRaw`
INSERT INTO "User" (name) VALUES ('John Doe')
`;
// 执行迁移
execSync("npx prisma migrate deploy");
// 验证拆分结果
const user = await prisma.user.findFirst();
expect(user?.firstName).toBe("John");
expect(user?.lastName).toBe("Doe");
});
it("应处理单个名字的情况", async () => {
await prisma.$executeRaw`
INSERT INTO "User" (name) VALUES ('Madonna')
`;
execSync("npx prisma migrate deploy");
const user = await prisma.user.findFirst({
where: { firstName: "Madonna" },
});
expect(user?.lastName).toBe("");
});
});Pre-Migration Checklist
迁移前检查清单
markdown
- [ ] Backup database
- [ ] Test migration on staging
- [ ] Verify data transformation logic
- [ ] Check for referential integrity issues
- [ ] Estimate migration time
- [ ] Plan rollback strategy
- [ ] Schedule maintenance window (if needed)
- [ ] Notify team of deploymentmarkdown
- [ ] 备份数据库
- [ ] 在预发布环境测试迁移
- [ ] 验证数据转换逻辑
- [ ] 检查引用完整性问题
- [ ] 估算迁移时间
- [ ] 制定回滚策略
- [ ] 安排维护窗口(若需要)
- [ ] 通知团队部署计划SQL Preview Script
SQL预览脚本
bash
#!/bin/bashbash
#!/bin/bashscripts/preview-migration.sh
scripts/preview-migration.sh
echo "🔍 Previewing migration..."
echo "🔍 正在预览迁移..."
Create migration without applying
创建迁移但不应用
npx prisma migrate dev --name "$1" --create-only
npx prisma migrate dev --name "$1" --create-only
Show SQL
展示SQL
echo ""
echo "📄 Generated SQL:"
echo "=================="
cat prisma/migrations/*_$1/migration.sql
echo ""
echo "📄 生成的SQL:"
echo "=================="
cat prisma/migrations/*_$1/migration.sql
Analyze impact
分析影响
echo ""
echo "📊 Impact Analysis:"
echo "=================="
echo "Tables affected: $(cat prisma/migrations/*_$1/migration.sql | grep -c 'ALTER TABLE')"
echo "Rows to update: [Run COUNT query manually]"
echo "Estimated time: [Estimate based on table size]"
undefinedecho ""
echo "📊 影响分析:"
echo "=================="
echo "受影响的表数量:$(cat prisma/migrations/*_$1/migration.sql | grep -c 'ALTER TABLE')"
echo "需更新的行数:[手动运行COUNT查询]"
echo "预估时间:[根据表大小估算]"
undefinedBest Practices
最佳实践
- Create migration, don't apply: Use flag
--create-only - Review SQL carefully: Check generated migration
- Test on staging: Always test before production
- Batch large updates: Avoid locking tables
- Add before removing: Additive migrations first
- Version application code: Deploy code that handles both schemas
- Monitor performance: Watch query times during migration
- Have rollback plan: Document reversal steps
- 创建迁移但不立即应用:使用参数
--create-only - 仔细审阅SQL:检查生成的迁移脚本
- 在预发布环境测试:生产环境前务必测试
- 批量处理大型更新:避免锁定表
- 先添加再删除:优先使用增量迁移
- 版本化应用代码:部署可兼容新旧schema的代码
- 监控性能:迁移期间关注查询耗时
- 制定回滚计划:记录回滚步骤
Output Checklist
输出检查清单
- Migration SQL generated and reviewed
- Data backfill strategy planned
- Rollback procedure documented
- Migration sequencing defined
- Testing plan created
- Impact analysis completed
- Staging deployment successful
- Production deployment scheduled
- 已生成并审阅迁移SQL
- 已规划数据回填策略
- 已记录回滚流程
- 已定义迁移排序
- 已创建测试计划
- 已完成影响分析
- 预发布环境部署成功
- 已安排生产环境部署时间