首页 > 解决方案 > 我不明白 GraphQL N+1 问题

问题描述

所以就在昨天我开始学习graphql,它真的很有趣,而且实际上很容易学习和理解。我开始阅读一些文章,我发现了 N+1 问题。我在这里找到了这个例子

询问

# getting the top 100 reviews
{
  top100Reviews {
    body
    author {
      name
    }
  }
}

架构


const typeDefs = gql`
  type User {
    id: ID!
    name: String
  }
  type Review {
    id: ID!
    body: String
    author: User
    product: Product
  }
  type Query {
    top100Reviews: [Review]
  }
`;

最后是解析器

const resolver = {
  Query: {
    top100Reviews: () => get100Reviews(),
  },
  Review: {
    author: (review) => getUser(review.authorId),
  },
};

他在这篇文章中说

当我们执行以下查询以获取前 100 条评论和相应的作者姓名时,我们首先进行一次调用以从数据库中检索 100 条评论记录,然后对于每条评论,我们再次调用数据库以获取用户详细信息给定作者ID。

我们不能只Review从解析器中删除并在 get100Reviews 方法中进行简单的 JOIN(如果我在 sql 中)

如果我们遇到 N+1 问题,我不明白为什么我们要做 Review 解析器,而我们可以在 Query 解析器中进行简单的 JOIN。

我理解 GraphQL 对吗?

请有人在这里阐明一下,并告诉我。

谢谢 !!

标签: expressgraphqlapollo-server

解决方案


你是对的——使用连接可以让你进行单个数据库查询而不是 101。

问题在于,在实践中,您不会只有一个连接——您的评论数据模型可能包括与任意数量的其他模型的关联,每个模型都需要自己的连接子句。不仅如此,这些模型可能与其他模型本身有关系。尝试创建一个 SQL 查询来处理所有可能的 GraphQL 查询不仅困难,而且成本高得令人望而却步。客户可能只请求没有关联模型的评论,但获取这些评论的查询现在包括 30 个额外的、不必要的视图。该查询可能花费了不到一秒的时间,但现在需要 10 秒。

还要考虑类型之间的关系可以是循环的:

{
  reviews {
    author {
      reviews {
        author
      }
    }
  }
}

在这种情况下,查询的深度是不确定的,并且不可能创建一个可以容纳任何可能的 GraphQL 查询的 SQL 查询。

使用像dataloader这样的库可以让我们通过批处理来缓解 N+1 问题,同时保持任何单个 SQL 查询尽可能精简。也就是说,您仍然会遇到多个查询。另一种方法是利用传递给解析器的 GraphQLResolveInfo 对象来确定首先请求了哪些字段。然后,如果您愿意,您可以只在查询中进行必要的联接。但是,解析info对象并构造此类查询可能是一项艰巨的任务,尤其是当您开始处理深度嵌套的关联时。另一方面,dataloader是一种更简单直观的解决方案。


推荐阅读