事务与批量操作:$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]
特点:
- 数组里的操作彼此独立,不能用前一个操作的返回值作为后一个的输入
- Prisma 会按顺序执行,中间任何一个失败就整体回滚
- 性能好——一次事务里发送所有语句
适用场景:多个互不依赖的写操作需要原子性(比如同时创建主表和关联表记录、批量删除多处数据)。
方式二:交互事务(复杂场景)
把一个函数传给 $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]
})
特点:
- 函数可以是
async,里面可以做任意逻辑判断 - 性能比批量事务差——函数里每个查询都是单独的 SQL 往返,不是一次批量发送
- 不要在交互事务里做非数据库操作(调第三方 API、读写文件)——事务占用期间数据库连接被锁住,做慢操作会导致超时
事务超时
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 } }),
])