Prisma 中文文档 下载 App

事务与批量操作:$transaction 的两种用法

Prisma 事务是什么

事务保证一组数据库操作要么全部成功,要么全部回滚。Prisma Client 的 $transaction 支持两种使用方式。

方式一:批量事务(推荐,简单场景)

把多个独立操作放在数组里,Prisma 把它们包在一个数据库事务里执行:

await prisma.$transaction([
  prisma.user.create({ data: { email: 'a@b.com' } }),
  prisma.post.create({ data: { title: '文章1', authorId: 1 } }),
  prisma.post.create({ data: { title: '文章2', authorId: 1 } }),
])
// 返回 [user, post1, post2]

特点:

适用场景:多个互不依赖的写操作需要原子性(比如同时创建主表和关联表记录、批量删除多处数据)。

方式二:交互事务(复杂场景)

把一个函数传给 $transaction,函数接收一个 tx 对象(tx 上也有 prisma 的所有查询方法)。你可以在函数里自由地先读、再判断、再写——整个函数体在同一个事务里执行:

const [user, posts] = await prisma.$transaction(async (tx) => {
  // 读:检查邮箱是否已存在
  const existing = await tx.user.findUnique({ where: { email: 'a@b.com' } })
  if (existing) throw new Error('Email already exists')

  // 写:创建用户
  const user = await tx.user.create({
    data: { email: 'a@b.com', name: 'A' },
  })

  // 再写:用刚创建的 user.id 写文章
  const posts = await tx.post.createMany({
    data: [
      { title: '文章1', authorId: user.id },
      { title: '文章2', authorId: user.id },
    ],
  })

  return [user, posts]
})

特点:

事务超时

await prisma.$transaction(async (tx) => {
  // ... 可能比较慢的操作
}, {
  maxWait: 5000,    // 等待获取连接的超时(ms)
  timeout: 10000,   // 事务执行超时(ms)
  isolationLevel: 'Serializable',  // 隔离级别(PostgreSQL 支持)
})

默认值:maxWait = 2000ms,timeout = 5000ms。如果你的事务里有多个慢查询,记得调大超时。

死锁与重试

并发场景下两个事务互相等待对方释放锁会导致死锁。Prisma Client 在死锁时会抛 P2034 错误:

Transaction failed due to a write conflict or a deadlock. Please retry your transaction

处理方式:循环重试

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (err) {
      if (i === maxRetries - 1) throw err
      // 指数退避等待一小段时间再重试
      await new Promise(res => setTimeout(res, Math.random() * 2 ** i * 100))
    }
  }
  throw new Error('Unreachable')
}

// 使用
const result = await withRetry(() =>
  prisma.$transaction(async (tx) => { /* ... */ })
)

不支持事务的场景

情况说明
SQLite完全支持(但 SQLite 本身单写者)
MongoDB支持,但 Prisma Client 需要配置 MongoDB 4.0+ 分片集群或副本集
Accelerate + Prisma Postgres事务功能完整支持
跨数据库不可能——一个事务只能操作同一个数据库

实战案例

案例一:转账

async function transferMoney(fromId: number, toId: number, amount: number) {
  await prisma.$transaction(async (tx) => {
    const from = await tx.account.update({
      where: { id: fromId },
      data: { balance: { decrement: amount } },
    })
    if (from.balance < 0) throw new Error('余额不足')

    await tx.account.update({
      where: { id: toId },
      data: { balance: { increment: amount } },
    })
  })
}

案例二:级联删除

await prisma.$transaction([
  prisma.comment.deleteMany({ where: { postId: postId } }),
  prisma.post.delete({ where: { id: postId } }),
])