Preview整个网站还在搭建中,当前包含较多草稿和未完成内容,暂未正式发布。
部署上线

数据库迁移

安全地迁移和更新数据库结构

数据库迁移是管理数据库结构变更的过程,确保开发、测试、生产环境的数据库结构一致。

为什么需要数据库迁移

  • 版本控制:数据库结构也需要版本管理
  • 团队协作:确保所有人的数据库结构一致
  • 安全部署:避免手动修改数据库导致错误
  • 可回滚:出问题时可以回退

迁移工具

Prisma(推荐)

优点

  • 类型安全
  • 自动生成迁移文件
  • 支持多种数据库
  • 开发体验好

Drizzle ORM

优点

  • 轻量级
  • SQL-like API
  • 性能好

Knex.js

优点

  • 灵活
  • 支持原生 SQL
  • 成熟稳定

快速开始:Prisma

1. 安装

npm install prisma @prisma/client
npx prisma init

2. 定义数据模型

// prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
}

3. 创建迁移

# 创建迁移文件
npx prisma migrate dev --name init

# Prisma 会:
# 1. 生成迁移 SQL
# 2. 执行迁移
# 3. 生成 Prisma Client

4. 查看迁移文件

-- prisma/migrations/20240101000000_init/migration.sql
CREATE TABLE "User" (
    "id" SERIAL PRIMARY KEY,
    "email" TEXT NOT NULL UNIQUE,
    "name" TEXT,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE "Post" (
    "id" SERIAL PRIMARY KEY,
    "title" TEXT NOT NULL,
    "content" TEXT,
    "published" BOOLEAN NOT NULL DEFAULT false,
    "authorId" INTEGER NOT NULL,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id")
);

5. 使用 Prisma Client

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

// 创建用户
const user = await prisma.user.create({
  data: {
    email: 'alice@example.com',
    name: 'Alice',
  },
})

// 查询
const users = await prisma.user.findMany({
  include: {
    posts: true,
  },
})

生产环境部署

1. 在 CI/CD 中运行迁移

# .github/workflows/deploy.yml
- name: Run migrations
  run: npx prisma migrate deploy
  env:
    DATABASE_URL: ${{ secrets.DATABASE_URL }}

2. 在部署平台运行

Vercel

package.json 中添加:

{
  "scripts": {
    "build": "prisma generate && prisma migrate deploy && next build"
  }
}

Railway

Railway 自动检测 Prisma,部署时自动运行迁移。

Fly.io

fly.toml 中:

[deploy]
  release_command = "npx prisma migrate deploy"

常见迁移场景

添加字段

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  // 新增字段
  avatar    String?
  bio       String?
  createdAt DateTime @default(now())
}
npx prisma migrate dev --name add_user_avatar_bio

修改字段

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String   // 改为必填
  createdAt DateTime @default(now())
}

注意:如果已有数据,需要先填充默认值。

删除字段

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  // 删除 name 字段
  createdAt DateTime @default(now())
}
npx prisma migrate dev --name remove_user_name

重命名字段

Prisma 会检测为删除 + 添加,需要手动修改迁移文件:

-- 手动修改迁移文件
ALTER TABLE "User" RENAME COLUMN "name" TO "fullName";

数据迁移

在迁移中修改数据

-- prisma/migrations/.../migration.sql
-- 添加字段
ALTER TABLE "User" ADD COLUMN "status" TEXT;

-- 填充默认值
UPDATE "User" SET "status" = 'active' WHERE "status" IS NULL;

-- 设置为必填
ALTER TABLE "User" ALTER COLUMN "status" SET NOT NULL;

使用 Prisma 脚本

// prisma/seed.ts
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  // 迁移数据
  const users = await prisma.user.findMany()
  
  for (const user of users) {
    await prisma.user.update({
      where: { id: user.id },
      data: {
        status: 'active',
      },
    })
  }
}

main()
npx tsx prisma/seed.ts

回滚迁移

Prisma

Prisma 不支持自动回滚,需要手动:

# 1. 查看迁移历史
npx prisma migrate status

# 2. 手动回滚(执行反向 SQL)
psql $DATABASE_URL < rollback.sql

# 3. 删除迁移记录
DELETE FROM "_prisma_migrations" WHERE migration_name = '20240101000000_add_field';

使用 Knex.js

Knex 支持回滚:

# 回滚最后一次迁移
npx knex migrate:rollback

# 回滚所有迁移
npx knex migrate:rollback --all

最佳实践

1. 小步迭代

每次迁移只做一件事,方便回滚。

2. 测试迁移

在测试环境先运行,确认无误后再部署生产。

3. 备份数据库

生产环境迁移前先备份:

# PostgreSQL
pg_dump $DATABASE_URL > backup.sql

# 恢复
psql $DATABASE_URL < backup.sql

4. 零停机迁移

对于大表,使用分步迁移:

  1. 添加新字段(可为空)
  2. 部署代码,同时写入新旧字段
  3. 迁移数据
  4. 切换到只使用新字段
  5. 删除旧字段

5. 监控迁移

  • 记录迁移时间
  • 监控数据库性能
  • 准备回滚方案

常见问题

迁移失败怎么办

  1. 查看错误信息
  2. 检查数据库状态
  3. 手动修复或回滚
  4. 重新运行迁移

本地和生产数据库不一致

# 重置本地数据库
npx prisma migrate reset

# 重新运行所有迁移
npx prisma migrate dev

如何处理冲突

多人同时修改数据库结构:

  1. 拉取最新代码
  2. 解决迁移冲突
  3. 创建新的迁移

数据库备份

自动备份

Railway

自动每天备份,保留 7 天。

Fly.io

使用 Fly Postgres:

flyctl postgres backup list
flyctl postgres backup restore <backup-id>

手动备份

# PostgreSQL
pg_dump $DATABASE_URL > backup-$(date +%Y%m%d).sql

# MySQL
mysqldump -u user -p database > backup.sql

下一步

目录