事务:批量、交互式与隔离级别
本教程共 54 篇 · 第 26 篇 · 更新于 2026-08-11 · 约 5 分钟阅读
本节目标:理解事务的原子性,掌握批量事务与交互式事务两种写法,学会配置超时和隔离级别。
为什么需要事务
转账是最经典的例子:从 A 的账户扣 100,给 B 的账户加 100。两步必须同时成功,或者同时失败。只扣了钱没加上,账就对不上了。数据库用事务(transaction)解决这个问题:一组读写操作要么全部提交(commit),要么全部回滚(rollback),不留中间状态。这就是 ACID 里的原子性。
Prisma Client 提供多种事务手段,按场景选择:
| 场景 | 手段 |
|---|---|
| 有依赖的写入(要用上一步生成的 ID) | 嵌套写入 |
| 相互独立的写入 | $transaction([]) 批量事务 |
| 读-改-写,中间有业务逻辑 | 交互式事务 |
嵌套写就是事务
一个 create 里嵌套创建关联记录,整个操作自动放进一个事务:
const user = await prisma.user.create({
data: {
email: "alice@example.com",
posts: { create: [{ title: "第一帖" }, { title: "第二帖" }] },
},
});
用户和两篇帖子要么全部创建,要么全部回滚。需要数据库生成 ID 才能继续写的场景,嵌套写是首选。批量操作也一样:createMany、updateMany、deleteMany 各自就是一个事务,一条语句更新一万行,中间出错整体回滚。
$transaction([]):批量事务
多笔相互独立的写入要同生共死,用数组形式:
const [deleted, created] = await prisma.$transaction([
prisma.post.deleteMany({ where: { authorId: 7 } }),
prisma.user.create({ data: { email: "bob@example.com" } }),
]);
数组里的操作按顺序逐个执行,全部成功才提交;任何一个抛错,前面已经执行的也全部回滚。典型场景是 GDPR 的「被遗忘权」:删用户之前先删他的帖子和私信,三个删除放进一个事务,要么全删干净,要么一个都不删。
注意数组形式的局限:操作在调用时就已构造好,没法根据前一个结果决定后一个操作。需要条件判断,就得用交互式事务。
交互式事务:读、判断、写
回调形式,可以在查询之间写任意业务逻辑:
await prisma.$transaction(async (tx) => {
const sender = await tx.account.update({
where: { email: "alice@example.com" },
data: { balance: { decrement: 100 } },
});
if (sender.balance < 0) {
throw new Error("余额不足,转账失败");
}
await tx.account.update({
where: { email: "bob@example.com" },
data: { balance: { increment: 100 } },
});
});
回调参数 tx 是事务客户端(TransactionClient)。tx 上的所有查询都在同一个事务里;函数正常结束自动提交,中途抛异常自动回滚,没有手动 rollback 的 API。余额不足时抛出错误,两个 update 都不会生效。
Note事务回调里必须用 tx 查询,不能混用外层 prisma。外层 prisma 的查询不在事务保护范围内。
超时与最大等待
交互式事务有两个时间选项:
await prisma.$transaction(
async (tx) => { /* 业务逻辑 */ },
{
maxWait: 5000, // 等待可用连接的最长时间,默认 2000ms
timeout: 10000, // 事务最长执行时间,默认 5000ms
},
);
超过 timeout 事务自动回滚并抛错。长事务一直占着连接、锁着行,所有其他写入都要排队。官方建议:事务快进快出,别在里面做慢操作。
隔离级别
事务并行执行时,数据库需要规则决定互相能看到什么。Prisma 支持五种隔离级别(isolation level):ReadUncommitted、ReadCommitted、RepeatableRead、Snapshot、Serializable。哪些可用取决于数据库:
| 数据库 | 可用级别 | 默认级别 |
|---|---|---|
| PostgreSQL | 除 Snapshot 外全部 | ReadCommitted |
| MySQL | 除 Snapshot 外全部 | RepeatableRead |
| SQLite | 仅 Serializable | Serializable |
| CockroachDB | 仅 Serializable | Serializable |
默认不设置时,用数据库自身的默认值。对一致性要求高的场景(比如并发扣库存),在第二个参数里指定:
{
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
}
Serializable 隔离最严,也最容易产生写冲突;冲突发生时 Prisma 返回 P2034 错误,应用层捕获后可以重试,下一章细讲。
Note交互式事务里把查询包进 Promise.all 不会并行。一个事务的所有查询共用一条连接,一条连接同一时刻只能执行一个查询,所以它们仍然是串行执行。
Tip本章内容适用于 SQL 数据库。MongoDB 不支持隔离级别,且 Prisma v7 已不支持 MongoDB(留 v6.19)。
参考来源
- Prisma 官方文档:Transactions and batch queries
- Mapagam:Managing Transactions
- DevSheets:Database Transactions
- Dev.to:Interactive Transactions in Prisma: A Developer’s Guide