首页 / Prisma ORM 入门教程 / 事务:批量、交互式与隔离级别

Prisma ORM 入门教程

事务:批量、交互式与隔离级别

本教程共 54 篇 · 第 26 篇 · 更新于 2026-08-11 · 约 5 分钟阅读

事务transaction交互式事务隔离级别ACID$transaction超时

本节目标:理解事务的原子性,掌握批量事务与交互式事务两种写法,学会配置超时和隔离级别。

为什么需要事务

转账是最经典的例子:从 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仅 SerializableSerializable
CockroachDB仅 SerializableSerializable

默认不设置时,用数据库自身的默认值。对一致性要求高的场景(比如并发扣库存),在第二个参数里指定:

{
  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