GraphQL 是如今前端领域中非常热门的技术,这是一种新兴的 API 语言,它具有较为强大的查询能力,一个 GraphQL 查询可以获取多个资源,这就是 GraphQL 所具有的天然优势。但是也正因此,它也存在一个问题:N+1 查询问题。
什么是 N+1 查询问题?
当我们使用 GraphQL 查询一个数据集合的时候,我们可能会遇到需要与每条 data 关联的其他数据,例如:
{ allUsers { id name posts { id title comments { id content } } } }
以上例子使用 GraphQL 查询所有用户,并查询他们的所有文章,还有每篇文章的评论。这种查询方式将查询数据分散成了多次查询,会导致 N+1 查询问题。
我们可以发现,在上面的查询中,首先会进行一次查询获取所有的用户,再进行 N 次查询获取每个用户的 posts 以及每篇文章的 comments。因此会进行 n+1 次查询,其中 n 表示 user 的数量。这种查询方式会造成后端系统压力上升,可能导致巨大的性能问题。
为了解决 GraphQL 中的 N+1 查询问题,我们可以使用 DataLoader 这个工具来进行优化。
DataLoader 是一个用于批量查询数据的 JavaScript 组件。它可以使用 batching 和 caching 来优化数据查询,从而避免 N+1 查询问题。
安装和配置 DataLoader
在使用 DataLoader 之前,需要先安装它。我们可以使用 npm 来安装 DataLoader:
npm install dataloader
接着,我们需要在 GraphQL 中进行 DataLoader 的配置。处理 GraphQL 请求的每个请求都应该有自己的 DataLoader 实例,这个实例应该是通过请求的上下文创建的。实例如下:
const { createServer } = require("http"); const { execute, subscribe } = require("graphql"); const { SubscriptionServer } = require("subscriptions-transport-ws"); const DataLoader = require("dataloader"); const schema = require("your-graphql-schema"); const dataLoader = new DataLoader(async (keys) => { const results = await Promise.all( keys.map((id) => getBatchedData(id)) ); return results; }); const context = { dataLoader, }; const server = createServer((req, res) => { // ... }); SubscriptionServer.create( { schema, execute, subscribe, onConnect: (connectionParams) => { return { dataLoader }; }, }, { server, path: "/" } ); server.listen(8080, () => console.log("Server running, open your browser on http://localhost:8080/") );
在上述代码中,我们首先要创建一个 DataLoader 实例 dataLoader
,然后定义一个上下文对象 context
,并赋值 dataLoader
。
使用 DataLoader 解决 N+1 查询问题
现在我们可以使用 DataLoader 来处理 GraphQL 中的 N+1 查询问题了。我们可以将每个具有相同 resolver 的 GraphQL 查询,以一个 batch 的形式,统一查询到同一个 DataLoader 里。这样就可以批量查询数据,而不是进行 N+1 查询,在数据量较大时能极大地提升性能。以下是实例代码:
const resolvePosts = async (users, _, { dataLoader }) => { const postIds = users.map(({ id }) => id); const posts = await dataLoader.loadMany(postIds); const postsByUserId = {}; posts.forEach((post) => { if (!postsByUserId[post.userId]) { postsByUserId[post.userId] = [post]; } else { postsByUserId[post.userId].push(post); } }); return users.map((user) => ({ ...user, posts: postsByUserId[user.id] || [], })); }; const resolveComments = async (posts, _, { dataLoader }) => { const commentIds = posts.reduce((acc, { comments }) => { acc.push(...comments); return acc; }, []); const comments = await dataLoader.loadMany(commentIds); const commentsByPostId = {}; comments.forEach((comment) => { if (!commentsByPostId[comment.postId]) { commentsByPostId[comment.postId] = [comment]; } else { commentsByPostId[comment.postId].push(comment); } }); return posts.map((post) => ({ ...post, comments: commentsByPostId[post.id] || [], })); };
在上述代码中,首先我们需要定义一个 resolvePosts
函数来查询所有的 post,并根据 userId 来将其分组。再定义一个函数 resolveComments
来查询所有的 comments,并根据 postId 来分组。这两个函数都会接受一个 dataLoader
的参数,给定将多个查询合并为一个查询的能力,从而帮助我们进行批处理操作。
总结
以上就是如何使用 DataLoader 工具解决 GraphQL 查询中的 N+1 查询问题的指导。掌握 N+1 查询问题的解决方法非常重要,因为它可以极大地提升 GraphQL 的性能,减轻后端服务器的压力。如果您正在使用 GraphQL,那么 DataLoader 工具会是您不可或缺的好伙伴。
参考资料:https://graphql.cn/code/#/code/batching-2
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6591c88feb4cecbf2d6cb95f