Prisma 中文文档 下载 App

性能优化:连接池、查询优化、避免常见陷阱

连接池

原理

Prisma Client 默认建立的连接池大小取决于你用的数据库驱动。连接池的目的是复用数据库连接,而不是每次查询都新建一个 TCP 连接。

数据库 URL 里控制

连接池大小主要由 DATABASE_URL 里的参数控制(Prisma Client 构造函数不直接暴露这个参数):

postgresql://user:pass@host:5432/mydb?connection_limit=10&pool_timeout=20
参数含义默认
connection_limit最大连接数PostgreSQL 默认取决于数据库
pool_timeout获取新连接的超时(秒)10
socket_timeout单个查询超时(秒)无

推荐配置

查看当前连接数

PostgreSQL 可以直接查:

SELECT count(*) FROM pg_stat_activity WHERE state = 'active';

如果你的连接数经常接近上限,要么调大 connection_limit,要么加独立连接池(Accelerate / PgBouncer)。

查询优化

1. 用 select 代替 include + 只取需要的字段

// ❌ 不好:include 会拉回所有普通字段 + 所有关联
const users = await prisma.user.findMany({
  include: { posts: true },
})

// ✅ 好:select 只取需要的字段
const users = await prisma.user.findMany({
  select: {
    id: true,
    email: true,
    posts: { select: { id: true, title: true } },
  },
})

2. 不要用 include 做计数

// ❌ 不好:拉回所有 Post 记录只为了算长度
const user = await prisma.user.findUnique({
  where: { id: 1 },
  include: { posts: true },
})
const postCount = user.posts.length

// ✅ 好:用 _count
const user = await prisma.user.findUnique({
  where: { id: 1 },
  select: { _count: { posts: true } },
})
const postCount = user._count.posts

3. 避免 N+1

Prisma 自动帮你处理了大部分场景。唯一的陷阱是在循环里单独查询:

// ❌ 不好:N+1
const users = await prisma.user.findMany()
for (const user of users) {
  const posts = await prisma.post.findMany({ where: { authorId: user.id } })
  // 每个用户一次查询
}

// ✅ 好:一次 include 拉回
const users = await prisma.user.findMany({ include: { posts: true } })

4. 复杂查询用事务 + 批量

如果你需要创建多条记录,用 createMany 或 $transaction 数组模式:

// ✅ 好:批量创建
await prisma.post.createMany({
  data: posts,
})

// ✅ 好:事务数组模式(一次事务里发多条 SQL)
await prisma.$transaction([
  prisma.post.create({ data: post1 }),
  prisma.post.create({ data: post2 }),
  prisma.post.create({ data: post3 }),
])

// ❌ 不好:循环里逐条创建(每条都是独立事务)
for (const post of posts) {
  await prisma.post.create({ data: post })
}

5. 原始 SQL 的坑

$queryRaw 和 $executeRaw 本身不是性能问题(它们就是直接发 SQL)。但:

事务超时

交互事务(传函数的 $transaction(async (tx) => {...}))默认 5 秒超时。事务里做慢操作(查第三方 API、处理大数组)会导致超时失败。

原则:事务里只做数据库操作,其他操作移到事务外。如果事务里的查询本身很慢,要么调大 timeout,要么优化查询本身。

await prisma.$transaction(async (tx) => {
  // 只做数据库操作
}, {
  maxWait: 5000,    // 等连接的超时
  timeout: 30000,   // 事务本身的超时 → 调大
})

生产环境关掉 query 日志

// ❌ 生产环境不要这样
export const prisma = new PrismaClient({
  log: ['query', 'error', 'warn'],
})

// ✅ 生产环境只记 error
export const prisma = new PrismaClient({
  log: ['error'],
})

log: ['query'] 会把每条 SQL 都打到 stdout。这不仅污染日志,还增加了序列化开销——高 QPS 下会很可观。

Prisma 内部的性能改进

Prisma 7 有显著的性能改进(官方 benchmark):

如果还在用 Prisma 5 或更早,考虑升级到 7。

监控 Prisma 查询耗时

开发环境下 log: ['query'] 直接看。生产环境推荐用 Client Extension:

export const prisma = new PrismaClient().$extends({
  query: {
    async $allOperations({ model, operation, args, query }) {
      const start = Date.now()
      const result = await query(args)
      const duration = Date.now() - start

      if (duration > 100) {
        console.warn(`[SLOW DB] ${model}.${operation} took ${duration}ms`, JSON.stringify(args))
      }

      return result
    },
  },
})

这样慢查询(超过 100ms)会被记录下来,方便定位。