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

如何解决NextJS中Prisma的UnknownJsonError(P6004)查询超时问题?

Prisma查询超时(P6004/P5000)问题分析与解决方案

问题原因

  • 重复查询与低效关联校验:当前逻辑执行两次findUnique查询,且在update操作中重复嵌套VerificationTokens.some条件。这种关联查询会触发数据库全表扫描(尤其是令牌校验环节),加上多次数据库往返,直接拉长请求时间导致超时。
  • Prisma客户端连接管理错误:在API路由的finally块中每次调用prisma.$disconnect(),但Next.js的API路由是无状态的,频繁创建/断开Prisma连接会产生额外开销,加剧超时风险。
  • 缓存策略误用:对令牌有效性这类需要实时校验的查询设置了cacheStrategy,缓存可能返回过期的令牌状态,同时缓存本身的处理逻辑也会增加请求延迟。
  • 数据库索引缺失:VerificationToken模型中,userId、expires字段未建立索引,关联查询和时间范围过滤时数据库无法快速定位数据,导致查询缓慢。

解决方案

1. 合并查询逻辑,减少数据库交互

将多次查询合并为一次,或使用事务保证原子性,避免重复校验相同条件:

import { PrismaClient } from '@prisma/client';
import { authOptions } from './auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
import { withAccelerate } from '@prisma/extension-accelerate';

// 全局单例Prisma客户端,避免重复实例化
const prisma = new PrismaClient().$extends(withAccelerate());

export default async function handler(req, res) {
    const session = await getServerSession(req, res, authOptions);

    try {
        const { email, verification } = req.body;
        const lowercaseEmail = email.toLowerCase();
        const trimVerification = verification.trim();

        if (!email || !verification) {
            return res.status(400).json({ message: 'An entry is missing' });
        }

        if (req.method === 'POST') {
            // 一次查询获取用户及关联的有效令牌
            const user = await prisma.user.findUnique({
                where: { email: lowercaseEmail },
                include: {
                    VerificationTokens: {
                        where: {
                            token: trimVerification,
                            expires: { gt: new Date() }
                        }
                    }
                }
            });

            if (!user) {
                return res.status(200).json({
                    message: 'User is not verified',
                    redirect: '/api/auth/signin',
                });
            }

            if (user.isVerified) {
                return res.status(200).json({
                    user,
                    message: 'User has been verified',
                    redirect: '/dashboard',
                });
            }

            // 检查是否存在有效令牌
            if (user.VerificationTokens.length > 0) {
                // 原子更新用户验证状态
                const updatedUser = await prisma.user.update({
                    where: { id: user.id },
                    data: { isVerified: true }
                });

                return res.status(200).json({
                    user: updatedUser,
                    message: 'User is verified',
                    redirect: '/dashboard',
                });
            }

            return res.status(200).json({
                message: 'User is not verified',
                redirect: '/api/auth/signin',
            });
        }
    } catch (error) {
        console.error('Error:', error);
        res.status(500).json({ message: 'There is an error' });
    }
    // 移除不必要的disconnect操作
}

2. 优化Prisma客户端实例

在Next.js中,Prisma客户端应该全局初始化一次,而非每次请求创建新实例并断开连接。移除finally块中的prisma.$disconnect(),避免频繁销毁连接产生的开销。

3. 调整缓存策略

移除所有验证相关查询的cacheStrategy,因为令牌有效性、用户验证状态需要实时校验,缓存会导致逻辑错误和额外延迟。

4. 添加数据库索引

修改schema.prisma,为VerificationToken添加必要索引:

model VerificationToken {
  id          String    @id @default(cuid())
  userId      String
  token       String    @unique
  expires     DateTime
  updatedAt   DateTime  @updatedAt
  user        User?     @relation(fields: [userId], references: [id])
  createdAt   DateTime  @default(now())
  activatedAt DateTime?

  @@unique([userId, token])
  // 添加索引加速关联查询和时间过滤
  @@index([userId])
  @@index([expires])
}

添加索引后,运行npx prisma migrate dev更新数据库结构。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 02:20:39