使用 Prisma Optimize 进行查询优化

使用 Prisma Optimize 进行查询优化

使用 Prisma Optimize 进行查询优化本指南展示了如何识别和优化查询性能、调试性能问题以及应对常见的挑战。

调试性能问题​

一些常见做法可能导致查询变慢和性能问题,例如:

过度获取数据

缺少索引

未缓存重复查询

执行全表扫描

信息有关更多潜在性能问题的原因,请访问此页面。

Prisma Optimize 提供建议,以识别和解决上述及更多低效率问题,从而帮助提高查询性能。

要开始使用,请遵循集成指南,将 Prisma Optimize 添加到您的项目,开始诊断慢查询。

提示您还可以在客户端级别记录查询事件,以查看生成的查询、它们的参数和执行时间。

如果您特别关注查询持续时间监控,请考虑使用日志中间件。

使用批量查询​

通常,批量读取和写入大量数据性能更好——例如,分批插入 50,000 条记录(每批 1000 条),而不是执行 50,000 次单独插入。PrismaClient 支持以下批量查询:

createMany()

createManyAndReturn()

deleteMany()

updateMany()

updateManyAndReturn()

findMany()

复用 PrismaClient 或使用连接池以避免数据库连接池耗尽​

创建多个 PrismaClient 实例可能会耗尽您的数据库连接池,尤其是在无服务器或边缘环境中,这可能会降低其他查询的速度。在无服务器挑战中了解更多信息。

对于具有传统服务器的应用程序,请实例化 PrismaClient 一次并在整个应用程序中复用它,而不是创建多个实例。例如,而不是:

query.tsasync function getPosts() { const prisma = new PrismaClient() await prisma.post.findMany()}async function getUsers() { const prisma = new PrismaClient() await prisma.user.findMany()}

在一个专用文件中定义一个单一的 PrismaClient 实例并重新导出以供复用

db.tsexport const prisma = new PrismaClient()

然后导入共享实例

query.tsimport { prisma } from "db.ts"async function getPosts() { await prisma.post.findMany()}async function getUsers() { await prisma.user.findMany()}

对于使用 HMR(热模块替换)框架的无服务器开发环境,请确保您在开发中正确处理Prisma 的单一实例。

解决 N+1 问题​

N+1 问题发生在您循环查询结果并为每个结果执行一个额外查询时,导致 n 个查询加上原始查询(N+1)。这是 ORM 的常见问题,特别是在与 GraphQL 结合使用时,因为您的代码生成效率低下的查询并不总是显而易见的。

使用 findUnique() 和 Prisma Client 的 dataloader 解决 GraphQL 中的 N+1 问题​

如果以下条件成立,Prisma Client dataloader 会自动对在同一tick 中发生且具有相同 where 和 include 参数的 findUnique() 查询进行批量处理:

where 过滤器的所有条件都在您查询的同一模型的标量字段(唯一或非唯一)上。

所有条件都使用 equal 过滤器,无论是通过简写还是显式语法 (where: { field: , field1: { equals: } })。

没有布尔运算符或关系过滤器。

findUnique() 的自动批量处理在 GraphQL 上下文中特别有用。GraphQL 为每个字段运行一个单独的解析器函数,这使得优化嵌套查询变得困难。

例如——以下 GraphQL 运行 allUsers 解析器以获取所有用户,并每个用户一次运行 posts 解析器以获取每个用户的帖子(N+1)

query { allUsers { id, posts { id } }}

allUsers 查询使用 user.findMany(..) 返回所有用户

const Query = objectType({ name: 'Query', definition(t) { t.nonNull.list.nonNull.field('allUsers', { type: 'User', resolve: (_parent, _args, context) => { return context.prisma.user.findMany() }, }) },})

这会产生一个单一的 SQL 查询

{ timestamp: 2021-02-19T09:43:06.332Z, query: 'SELECT `dev`.`User`.`id`, `dev`.`User`.`email`, `dev`.`User`.`name` FROM `dev`.`User` WHERE 1=1 LIMIT ? OFFSET ?', params: '[-1,0]', duration: 0, target: 'quaint::connector::metrics'}

但是,posts 的解析器函数会每个用户一次被调用。这导致每个用户而不是单个 findMany() 返回所有用户的所有帖子(展开 CLI 输出以查看查询)的 findMany() 查询 ✘。

const User = objectType({ name: 'User', definition(t) { t.nonNull.int('id') t.string('name') t.nonNull.string('email') t.nonNull.list.nonNull.field('posts', { type: 'Post', resolve: (parent, _, context) => { return context.prisma.post.findMany({ where: { authorId: parent.id || undefined }, }) }, }) },})显示CLI结果

解决方案 1:使用 Fluent API 批量查询​

结合使用 findUnique() 和流畅 API (.posts()),如所示返回用户的帖子。即使解析器每个用户调用一次,Prisma Client 中的 Prisma dataloader ✔ 会批量处理 findUnique() 查询。

信息使用 prisma.user.findUnique(...).posts() 查询返回帖子而不是 prisma.posts.findMany() 可能看起来有悖常理——特别是前者会导致两次查询而不是一次。

您需要使用流畅 API (user.findUnique(...).posts()) 返回帖子的唯一原因是 Prisma Client 中的 dataloader 批量处理 findUnique() 查询,但目前不会批量处理 findMany() 查询。

当 dataloader 批量处理 findMany() 查询或您的查询将 relationStrategy 设置为 join 时,您不再需要以这种方式将 findUnique() 与流畅 API 一起使用。

const User = objectType({ name: 'User', definition(t) { t.nonNull.int('id') t.string('name') t.nonNull.string('email') t.nonNull.list.nonNull.field('posts', { type: 'Post', resolve: (parent, _, context) => { return context.prisma.post.findMany({ where: { authorId: parent.id || undefined }, }) return context.prisma.user .findUnique({ where: { id: parent.id || undefined }, }) .posts() }, }) },})显示CLI结果

如果 posts 解析器每个用户调用一次,Prisma Client 中的 dataloader 会将具有相同参数和选择集的 findUnique() 查询分组。每个组都会被优化为一个单一的 findMany()。

解决方案 2:使用 JOIN 执行查询​

您可以通过将 relationLoadStrategy 设置为 "join" 来使用数据库连接执行查询,确保只对数据库执行一次查询。

const User = objectType({ name: 'User', definition(t) { t.nonNull.int('id') t.string('name') t.nonNull.string('email') t.nonNull.list.nonNull.field('posts', { type: 'Post', resolve: (parent, _, context) => { return context.prisma.post.findMany({ relationLoadStrategy: "join", where: { authorId: parent.id || undefined }, }) }, }) },})

其他上下文中的 N+1 问题​

N+1 问题最常见于 GraphQL 上下文,因为您必须找到一种方法来优化跨多个解析器的单个查询。然而,您也可以通过在自己的代码中使用 forEach 循环结果轻松引入 N+1 问题。

以下代码导致 N+1 查询——一个 findMany() 获取所有用户,以及每个用户一个 findMany() 获取每个用户的帖子

// One query to get all usersconst users = await prisma.user.findMany({})// One query PER USER to get all postsusers.forEach(async (usr) => { const posts = await prisma.post.findMany({ where: { authorId: usr.id, }, }) // Do something with each users' posts})显示CLI结果SELECT "public"."User"."id", "public"."User"."email", "public"."User"."name" FROM "public"."User" WHERE 1=1 OFFSET $1SELECT "public"."Post"."id", "public"."Post"."title" FROM "public"."Post" WHERE "public"."Post"."authorId" = $1 OFFSET $2SELECT "public"."Post"."id", "public"."Post"."title" FROM "public"."Post" WHERE "public"."Post"."authorId" = $1 OFFSET $2SELECT "public"."Post"."id", "public"."Post"."title" FROM "public"."Post" WHERE "public"."Post"."authorId" = $1 OFFSET $2SELECT "public"."Post"."id", "public"."Post"."title" FROM "public"."Post" WHERE "public"."Post"."authorId" = $1 OFFSET $2/* ..and so on .. */

这不是一种高效的查询方式。相反,您可以:

使用嵌套读取(include)返回用户和相关帖子

使用in过滤器

将relationLoadStrategy设置为"join"

使用 include 解决 N+1 问题​

您可以使用 include 返回每个用户的帖子。这只会产生两个 SQL 查询——一个用于获取用户,另一个用于获取帖子。这被称为嵌套读取。

const usersWithPosts = await prisma.user.findMany({ include: { posts: true, },})显示CLI结果SELECT "public"."User"."id", "public"."User"."email", "public"."User"."name" FROM "public"."User" WHERE 1=1 OFFSET $1SELECT "public"."Post"."id", "public"."Post"."title", "public"."Post"."authorId" FROM "public"."Post" WHERE "public"."Post"."authorId" IN ($1,$2,$3,$4) OFFSET $5

使用 in 解决 N+1 问题​

如果您有一个用户 ID 列表,可以使用 in 过滤器返回 authorId 在该 ID 列表中的所有帖子

const users = await prisma.user.findMany({})const userIds = users.map((x) => x.id)const posts = await prisma.post.findMany({ where: { authorId: { in: userIds, }, },})显示CLI结果SELECT "public"."User"."id", "public"."User"."email", "public"."User"."name" FROM "public"."User" WHERE 1=1 OFFSET $1SELECT "public"."Post"."id", "public"."Post"."createdAt", "public"."Post"."updatedAt", "public"."Post"."title", "public"."Post"."content", "public"."Post"."published", "public"."Post"."authorId" FROM "public"."Post" WHERE "public"."Post"."authorId" IN ($1,$2,$3,$4) OFFSET $5

使用 relationLoadStrategy: "join" 解决 N+1 问题​

您可以通过将 relationLoadStrategy 设置为 "join" 来使用数据库连接执行查询,确保只对数据库执行一次查询。

const users = await prisma.user.findMany({})const userIds = users.map((x) => x.id)const posts = await prisma.post.findMany({ relationLoadStrategy: "join", where: { authorId: { in: userIds, }, },})

相关推荐

蜗牛搬家的故事你想对小蜗牛说什么
365彩票老版本

蜗牛搬家的故事你想对小蜗牛说什么

📅 08-16 👁️ 2838
王字旁加一个睿是什么字,王字旁加一个睿念什么
365彩票老版本

王字旁加一个睿是什么字,王字旁加一个睿念什么

📅 08-09 👁️ 6581
槿慕轩家具怎么样,好坏判断,你要绕过这几个坑