4.5.3 改表结构时数据怎么办——数据迁移:结构变更时的数据处理
一句话破题
数据迁移是结构迁移的延伸——不仅要改表结构,还要确保现有数据正确转换到新结构。
常见数据迁移场景
| 场景 | 挑战 | 解决方案 |
|---|---|---|
| 添加非空字段 | 现有数据没有值 | 设置默认值或先填充数据 |
| 重命名字段 | 数据需要复制 | 添加新字段 → 复制数据 → 删除旧字段 |
| 拆分表 | 数据需要分发 | 创建新表 → 迁移数据 → 建立关联 |
| 合并表 | 数据需要整合 | 创建新表 → 合并数据 → 删除旧表 |
场景一:添加非空字段
问题:现有数据没有新字段的值
prisma
model User {
id String @id
email String
role String // 新增非空字段
}解决方案:分步迁移
bash
# 步骤 1: 添加可空字段
npx prisma migrate dev --create-only --name add_role_nullablesql
-- migration.sql
ALTER TABLE "User" ADD COLUMN "role" TEXT;bash
# 步骤 2: 填充数据
npx prisma migrate dev --create-only --name fill_role_datasql
-- migration.sql
UPDATE "User" SET "role" = 'USER' WHERE "role" IS NULL;bash
# 步骤 3: 设置非空约束
npx prisma migrate dev --create-only --name make_role_requiredsql
-- migration.sql
ALTER TABLE "User" ALTER COLUMN "role" SET NOT NULL;场景二:重命名字段
sql
-- 步骤 1: 添加新字段
ALTER TABLE "User" ADD COLUMN "fullName" TEXT;
-- 步骤 2: 复制数据
UPDATE "User" SET "fullName" = "name";
-- 步骤 3: 删除旧字段(可选,下次迁移)
ALTER TABLE "User" DROP COLUMN "name";场景三:拆分一对多关系
将 User 表中的 addresses 数组拆分为独立的 Address 表:
sql
-- 步骤 1: 创建新表
CREATE TABLE "Address" (
"id" TEXT PRIMARY KEY,
"userId" TEXT NOT NULL REFERENCES "User"("id"),
"street" TEXT,
"city" TEXT
);
-- 步骤 2: 迁移数据(应用层处理)
-- 需要编写脚本遍历 User.addresses 并插入到 Address 表
-- 步骤 3: 删除旧字段
ALTER TABLE "User" DROP COLUMN "addresses";编写数据迁移脚本
typescript
// scripts/migrate-data.ts
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function migrateUserRoles() {
const users = await prisma.user.findMany({
where: { role: null }
})
console.log(`Found ${users.length} users without role`)
for (const user of users) {
await prisma.user.update({
where: { id: user.id },
data: { role: 'USER' }
})
}
console.log('Migration completed')
}
migrateUserRoles()
.catch(console.error)
.finally(() => prisma.$disconnect())运行脚本:
bash
npx tsx scripts/migrate-data.ts大数据量迁移策略
批量处理:
typescript
async function batchMigrate() {
const batchSize = 1000
let processed = 0
while (true) {
const users = await prisma.user.findMany({
where: { role: null },
take: batchSize
})
if (users.length === 0) break
await prisma.user.updateMany({
where: { id: { in: users.map(u => u.id) } },
data: { role: 'USER' }
})
processed += users.length
console.log(`Processed ${processed} users`)
}
}迁移顺序建议
验证数据迁移
typescript
async function verifyMigration() {
const nullRoleCount = await prisma.user.count({
where: { role: null }
})
if (nullRoleCount > 0) {
throw new Error(`Still have ${nullRoleCount} users without role`)
}
console.log('Verification passed!')
}本节小结
- 数据迁移通常需要分多步执行
- 添加非空字段:先可空 → 填充数据 → 设非空
- 重命名字段:添加新 → 复制数据 → 删除旧
- 大数据量使用批量处理
- 迁移后务必验证数据
