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 查询 | 使用 include 或 select |
| 返回数据过多 | 使用 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 - 开启日志排查慢查询
