跳转到内容

瞬态错误重试(prisma-retry)

Prisma 的 $queryRaw / $executeRaw 不会经过 $extends$allModels.$allOperations 拦截器, 因此软删除、审计等扩展对原生 SQL 无效。同样,瞬态网络错误也无法通过中间件自动重试。

wrapWithRetryableRaw 直接在 PrismaClient 实例上包装 $queryRaw$executeRaw,为原生 SQL 添加指数退避重试。

快速开始

typescript
import { PrismaClient } from '@prisma/client'
import { wrapWithRetryableRaw } from '@maxtan/nest-core'

const prisma = new PrismaClient()

// 在 client 初始化后调用
wrapWithRetryableRaw(prisma)

// 之后所有 $queryRaw / $executeRaw 自动具备重试能力
const result = await prisma.$queryRaw`SELECT * FROM users`

配置选项

typescript
import { wrapWithRetryableRaw, type PrismaRetryOptions } from '@maxtan/nest-core'

const options: PrismaRetryOptions = {
  maxRetries: 2,      // 最大重试次数,默认 2
  baseDelayMs: 100    // 退避基础延迟(ms),默认 100
}

wrapWithRetryableRaw(prisma, options)
参数类型默认值说明
maxRetriesnumber2最大重试次数(不含首次调用)
baseDelayMsnumber100指数退避基础延迟(ms),实际延迟 = baseDelayMs × 2^attempt

可重试的错误

以下错误类型会触发自动重试:

类型说明
P2010Prisma 原生 SQL 执行失败(Raw query failed)
ECONNRESET连接被重置
ETIMEDOUT连接超时
EPIPE管道断裂
DriverAdapterError数据库驱动适配器错误
通信包错误writing/reading communication packetsconnection resetconnection timed outbroken pipe

非瞬态错误(如 SQL 语法错误、约束违反等)会立即抛出,不触发重试。

重试行为

第 1 次调用失败 → 等待 100ms → 第 1 次重试
第 1 次重试失败 → 等待 200ms → 第 2 次重试
第 2 次重试失败 → 等待 400ms → 第 3 次重试(如果 maxRetries >= 3)
...
达到最大重试次数 → 抛出最后一次错误

每次重试会通过框架内置 logger 输出警告日志:

[PrismaRetry] 瞬时错误第 1 次重试 (100ms): Raw query failed...

与 PrismaModule 集成

PrismaBaseService 的生命周期中集成重试:

typescript
import { PrismaBaseService } from '@maxtan/nest-core'
import { wrapWithRetryableRaw } from '@maxtan/nest-core'

@Injectable()
export class AppPrismaService extends PrismaBaseService {
  async onModuleInit() {
    await super.onModuleInit()
    // 获取底层 PrismaClient 并包装重试
    wrapWithRetryableRaw(this.prisma as any, { maxRetries: 3 })
  }
}

注意事项

  • wrapWithRetryableRaw 直接修改 client 实例的 $queryRaw / $executeRaw 方法,不是 $extends 扩展
  • 仅覆盖原生 SQL 操作($queryRaw / $executeRaw),Model 操作(findMany 等)不受影响
  • 重试延迟使用指数退避,避免雪崩式重试压垮数据库
  • 日志通过框架内置 logger 输出,确保与项目日志系统一致

相关文档