You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS集成Prisma(Postgres)时RefreshToken操作耗时飙升问题排查

问题描述

使用NestJS结合Prisma(PostgreSQL)开发时,最初仅为UserEntity创建仓库,运行状态正常。新增RefreshToken存储及对应仓库/服务后,在FitnessUserService中注入RefreshTokenService并调用其createRefreshSession方法,请求耗时从原本170ms急剧上升至50秒,需恢复性能至正常水平。

Prisma Schema

generator client {
  provider = "prisma-client-js"
  output   = "../../../node_modules/.prisma/client"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

enum UserRole {
  trainer
  client
}

model UserEntity {
  userId            Int                     @id @default(autoincrement()) @map("user_id")
  userName          String                  @default("") @map("user_name")
  userMail          String                  @unique @default("") @map("user_mail")
  userAvatar        String                  @default("") @map("user_avatar")
  passwordHash      String                  @default("")
  userGender        String                  @default("") @map("user_gender")
  birthDate         String                  @default("") @map("birth_date")
  userRole          UserRole                @map("user_role")
  description       String                  @default("")
  location          String                  @default("")
  backgraundPicture String                  @default("") @map("backgraund_picture")
  createdAt         DateTime                @default(now()) @map("created_at")
  clientBody        ClientBody?
  trainerBody       TrainerBody?
  levelOfExperience String                  @default("") @map("level_of_experience")

  @@map("users")
}

model ClientBody {
  clientBodyId          Int        @id @default(autoincrement()) @map("user_body_id")
  user                  UserEntity @relation(fields: [userId], references: [userId], onDelete: Cascade, onUpdate: Cascade)
  userId                Int        @unique @map("user_id")
  timeOfTraining        String     @default("") @map("time_of_training")
  caloryLosingPlanTotal Int        @default(0) @map("calory_losing_plan_total")
  caloryLosingPlanDaily Int        @default(0) @map("calory_losing_plan_daily")
  readinessForTraining  Boolean    @default(false) @map("readiness_for_training")

  @@map("client_body")
}

model TrainerBody {
  trainerBodyId       Int        @id @default(autoincrement()) @map("trainer_body_id")
  user                UserEntity @relation(fields: [userId], references: [userId], onDelete: Cascade, onUpdate: Cascade)
  userId              Int        @unique @map("user_id")
  sertificate         String     @default("")
  merit               String     @default("")
  readinessForPrivate Boolean    @default(false) @map("readiness_for_private")

  @@map("trainer_body")
}

model RefreshToken {
  refreshTokenId Int      @id @default(autoincrement()) @map("refresh_token_id")
  tokenId        String   @map("token_id")
  userId         String   @map("user_id")
  createdAt      DateTime @default(now()) @map("created_at")
  expiresIn      DateTime @default(now()) @map("expires_in")

  @@map("refresh_token_store")
}

相关服务代码

@Injectable()
export class FitnessUserService {
  constructor(
    private readonly fitnessUserRepository: RefreshTokenService,
    private readonly jwtService: JwtService,
    private readonly refreshTokenService: RefreshTokenService,
    @Inject(jwtConfig.KEY)
    private readonly jwtOptions: ConfigType<typeof jwtConfig>
  ) {}


  public async createUserToken(user: User) {
    console.log({ user });
    const accessTokenPayload = createJWTPayload(user);
    const refreshTokenPayload = {
      ...accessTokenPayload,
      tokenId: crypto.randomUUID(),
    };
    //此处调用后耗时从120ms飙升至50000ms
    await this.refreshTokenService.createRefreshSession(refreshTokenPayload);

    return {
      accessToken: await this.jwtService.signAsync(accessTokenPayload),
      refreshToken: await this.jwtService.signAsync(refreshTokenPayload, {
        secret: this.jwtOptions.refreshTokenSecret,
        expiresIn: this.jwtOptions.refreshTokenExpiresIn,
      }),
    };
  }
}

解决方案

1. 修复依赖注入的类型错误

FitnessUserService构造函数中,fitnessUserRepository被错误注入为RefreshTokenService,这会导致不必要的实例冲突或循环依赖,直接拖慢性能:

// 修正前
constructor(
  private readonly fitnessUserRepository: RefreshTokenService, // 类型不匹配
  // ...其他注入项
) {}

// 修正后
constructor(
  private readonly fitnessUserRepository: FitnessUserRepository, // 替换为正确的用户仓库类型
  private readonly jwtService: JwtService,
  private readonly refreshTokenService: RefreshTokenService,
  @Inject(jwtConfig.KEY)
  private readonly jwtOptions: ConfigType<typeof jwtConfig>
) {}

2. 修正RefreshToken模型的字段类型不匹配

UserEntity的userId是Int类型,但RefreshToken的userId定义为String,插入时PostgreSQL会进行隐式类型转换,大量转换会严重降低写入速度:

// 修正前
model RefreshToken {
  userId         String   @map("user_id")
  // ...其他字段
}

// 修正后
model RefreshToken {
  userId         Int      @map("user_id") // 与UserEntity的userId类型保持一致
  // ...其他字段
}

修改后执行prisma migrate dev更新数据库表结构。

3. 为RefreshToken表添加必要索引

针对高频操作的字段添加索引,提升写入和查询性能:

model RefreshToken {
  refreshTokenId Int      @id @default(autoincrement()) @map("refresh_token_id")
  tokenId        String   @map("token_id") @unique // 防止重复token,加速唯一性校验
  userId         Int      @map("user_id")
  createdAt      DateTime @default(now()) @map("created_at")
  expiresIn      DateTime @default(now()) @map("expires_in")

  @@map("refresh_token_store")
  @@index([userId]) // 为userId添加索引,后续查询用户的refresh token时更快
}

4. 精简createRefreshSession方法实现

确保该方法仅执行必要的插入操作,避免多余查询或循环:

// RefreshTokenService中的示例实现
async createRefreshSession(payload: { userId: number; tokenId: string; expiresIn: Date }) {
  return this.prisma.refreshToken.create({
    data: {
      userId: payload.userId,
      tokenId: payload.tokenId,
      expiresIn: payload.expiresIn,
    },
  });
}

5. 优化数据库连接配置

在schema.prisma中调整连接池大小,适配服务器性能:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
  pool_size = 10 // 根据服务器配置调整,默认值为5
}

同时可开启PostgreSQL慢查询日志,定位是否存在数据库层面的瓶颈。


内容的提问来源于stack exchange,提问作者IvMerk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 07:47:45