为什么Prisma的deleteMany方法带where条件时会执行两次查询?
我注意到当指定
where条件时,deleteMany会执行两次查询:首先查询待删除行的主键,再通过DELETE FROM WHERE id IN (...)语句删除对应行。
这样设计的作用是什么?相比这种先查再删的逻辑,我认为直接在DELETE查询本身指定待删除行的过滤条件会更合理。
举个例子:await this.prismaService.cardSet.deleteMany({ where: { steamAccountId: steamAccount.id } });这段代码实际执行的SQL如下:
SELECT "public"."CardSet"."id" FROM "public"."CardSet" WHERE "public"."CardSet"."steamAccountId" = $1; DELETE FROM "public"."CardSet" WHERE "public"."CardSet"."id" IN ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,...);我认为如下写法的执行效率更高:
DELETE FROM "public"."CardSet" WHERE "public"."CardSet"."steamAccountId" = $1;
设计原因说明
这个两步删除的逻辑是Prisma ORM的有意设计,核心出于以下几点考量:
- 兼容关联逻辑与钩子触发:如果你的模型配置了级联删除、字段级监听事件,或者使用了Prisma中间件监听单条记录的删除操作,先查询待删除记录的主键和对应数据,才能准确触发所有绑定的业务逻辑,也能确保关联表的级联操作完全符合你在Schema中定义的规则,不会出现直接删主表导致关联表数据异常的问题。
- 事务与锁优化:当你的删除条件涉及多表关联、或者过滤逻辑非常复杂时,直接执行带复杂WHERE的DELETE语句会长时间持有行锁甚至表锁,在高并发场景下很容易引发死锁。先查询主键再分批删除的逻辑可以拆分大事务,大幅缩短锁的持有时间,降低并发冲突概率。
- 返回结果准确:Prisma的
deleteMany会返回实际删除的记录条数,受数据库事务隔离级别、并发写入的影响,直接执行DELETE后数据库返回的影响行数可能和实际匹配你过滤条件的行数有偏差。先查主键再删除的方式可以完全匹配你查询时刻符合条件的记录,避免幻读导致的计数不准问题。
高效单步删除的实现方式
如果你确定当前场景不需要触发记录级别的钩子、也没有复杂的级联逻辑,可以直接用Prisma的原生SQL方法执行你提到的高效DELETE语句,示例如下:
await this.prismaService.$executeRaw` DELETE FROM "public"."CardSet" WHERE "steamAccountId" = ${steamAccount.id} `;
如果你用的是Prisma 4.16及以上版本,也可以通过配置全局关闭非必要的两步删除逻辑,开启后deleteMany会直接生成带WHERE条件的单步DELETE语句,需要注意的是开启后绑定在单条记录上的中间件、删除钩子将不会生效。
内容的提问来源于stack exchange,提问作者Tim
相关产品推荐
相关产品推荐

