⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议
Skip to content

4.5.3 改表结构时数据怎么办——数据迁移:结构变更时的数据处理

一句话破题

数据迁移是结构迁移的延伸——不仅要改表结构,还要确保现有数据正确转换到新结构。

常见数据迁移场景

场景挑战解决方案
添加非空字段现有数据没有值设置默认值或先填充数据
重命名字段数据需要复制添加新字段 → 复制数据 → 删除旧字段
拆分表数据需要分发创建新表 → 迁移数据 → 建立关联
合并表数据需要整合创建新表 → 合并数据 → 删除旧表

场景一:添加非空字段

问题:现有数据没有新字段的值

prisma
model User {
  id    String @id
  email String
  role  String // 新增非空字段
}

解决方案:分步迁移

bash
# 步骤 1: 添加可空字段
npx prisma migrate dev --create-only --name add_role_nullable
sql
-- migration.sql
ALTER TABLE "User" ADD COLUMN "role" TEXT;
bash
# 步骤 2: 填充数据
npx prisma migrate dev --create-only --name fill_role_data
sql
-- migration.sql
UPDATE "User" SET "role" = 'USER' WHERE "role" IS NULL;
bash
# 步骤 3: 设置非空约束
npx prisma migrate dev --create-only --name make_role_required
sql
-- 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!')
}

本节小结

  • 数据迁移通常需要分多步执行
  • 添加非空字段:先可空 → 填充数据 → 设非空
  • 重命名字段:添加新 → 复制数据 → 删除旧
  • 大数据量使用批量处理
  • 迁移后务必验证数据