⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议
Skip to content

4.4.8 查询如何更高效——高级查询技巧:关联查询与性能优化

一句话破题

Prisma 查询优化的核心是减少查询次数和返回数据量——学会这些技巧能让你的应用快 10 倍。

避免 N+1 问题

问题代码

typescript
// N+1 问题:100 个用户会产生 101 次查询
const users = await prisma.user.findMany()
for (const user of users) {
  const posts = await prisma.post.findMany({
    where: { authorId: user.id }
  })
}

解决方案:使用 include

typescript
// 只有 1 次查询
const users = await prisma.user.findMany({
  include: { posts: true }
})

select vs include

方法作用场景
include加载关联数据需要完整的关联对象
select选择特定字段只需要部分字段
typescript
// include:返回所有字段 + 关联数据
const user = await prisma.user.findUnique({
  where: { id },
  include: { posts: true }
})

// select:只返回需要的字段
const user = await prisma.user.findUnique({
  where: { id },
  select: {
    id: true,
    name: true,
    posts: {
      select: { id: true, title: true }
    }
  }
})

条件查询技巧

复杂条件

typescript
const posts = await prisma.post.findMany({
  where: {
    AND: [
      { status: 'PUBLISHED' },
      { authorId: userId }
    ],
    OR: [
      { title: { contains: keyword } },
      { content: { contains: keyword } }
    ],
    NOT: { deletedAt: { not: null } }
  }
})

动态条件构建

typescript
function buildFilter(params: {
  status?: string
  authorId?: string
  keyword?: string
}) {
  const where: Prisma.PostWhereInput = {}
  
  if (params.status) {
    where.status = params.status
  }
  if (params.authorId) {
    where.authorId = params.authorId
  }
  if (params.keyword) {
    where.OR = [
      { title: { contains: params.keyword } },
      { content: { contains: params.keyword } }
    ]
  }
  
  return where
}

const posts = await prisma.post.findMany({
  where: buildFilter({ status: 'PUBLISHED', keyword: 'prisma' })
})

分页最佳实践

Offset 分页(简单但大数据量时慢):

typescript
const posts = await prisma.post.findMany({
  skip: (page - 1) * pageSize,
  take: pageSize,
  orderBy: { createdAt: 'desc' }
})

const total = await prisma.post.count()

Cursor 分页(性能更好):

typescript
const posts = await prisma.post.findMany({
  take: pageSize,
  skip: cursor ? 1 : 0,  // 跳过 cursor 本身
  cursor: cursor ? { id: cursor } : undefined,
  orderBy: { id: 'asc' }
})

const nextCursor = posts.length === pageSize 
  ? posts[posts.length - 1].id 
  : null

批量操作优化

批量创建

typescript
// 使用 createMany 而非循环 create
await prisma.user.createMany({
  data: users,
  skipDuplicates: true
})

批量更新

typescript
await prisma.post.updateMany({
  where: { status: 'DRAFT', createdAt: { lt: oneMonthAgo } },
  data: { status: 'ARCHIVED' }
})

批量删除

typescript
await prisma.post.deleteMany({
  where: { authorId: userId }
})

开启查询日志

typescript
const prisma = new PrismaClient({
  log: [
    { level: 'query', emit: 'event' }
  ]
})

prisma.$on('query', (e) => {
  console.log(`Query: ${e.query}`)
  console.log(`Duration: ${e.duration}ms`)
})

性能优化检查清单

问题解决方案
N+1 查询使用 includeselect
返回数据过多使用 select 只选需要的字段
分页性能差使用 Cursor 分页替代 Offset
查询太慢检查是否缺少索引
批量操作慢使用 createMany/updateMany

使用原生 SQL

当 Prisma API 无法满足需求时:

typescript
// 原生查询
const result = await prisma.$queryRaw`
  SELECT u.*, COUNT(p.id) as post_count
  FROM users u
  LEFT JOIN posts p ON p.author_id = u.id
  GROUP BY u.id
  ORDER BY post_count DESC
  LIMIT 10
`

// 原生执行(无返回值)
await prisma.$executeRaw`
  UPDATE posts SET view_count = view_count + 1 WHERE id = ${postId}
`

本节小结

  • 使用 include 避免 N+1 问题
  • 使用 select 减少返回数据量
  • 大数据量分页使用 Cursor 而非 Offset
  • 批量操作使用 createMany/updateMany
  • 开启日志排查慢查询