GraphQL 是目前前端领域中非常热门的技术,它可以帮助开发人员更高效地查询及获取后端数据。然而,在 GraphQL 查询中,N+1 查询问题可能会成为一个难以避免的问题。N+1 查询问题是指在 GraphQL 查询中,当要查询多个相关对象时,会导致沿着关系链进行多次查询,因此,如果查询的对象很多,就会导致响应时间变慢,甚至可能导致系统崩溃。本文将介绍如何解决 GraphQL 查询中防止 N+1 查询问题的最佳实践。
什么是 N+1 查询问题?
N+1 查询问题可以简单地理解为一个常见的性能问题,当要查询多个相关对象时,会导致沿着关系链进行多次查询。例如,在查询一个作者的图书时,如果我们使用 GraphQL 查询语句:
{
authors {
name
books {
title
}
}
}如果 authors 是一个数组,那么对于每个作者,GraphQL 将会发送一个查询以获取对应图书的详细信息,这将导致由于查询数大于 1,填充关系可能需要进行 N+1 次查询。由于每个查询的成本都很高,并且只需要一个请求即可获取所有图书的详细信息,我们需要找到一种解决办法来避免这种 N+1 查询的性能问题。
如何避免 N+1 查询问题?
使用 DataLoader
由于 GraphQL 采用树形查询方式,解决 N+1 查询问题的好方法是使用 DataLoader。DataLoader 可以将需要加载的数据分组成一批批加载,直到满足 GraphQL 查询所需的性能要求。它是一个为 Node.js 和浏览器构建的延迟加载数据批处理库。我们可以使用 DataLoader 来解决 N+1 查询问题,节约资源,避免需要发送多个请求。
首先,需要安装 DataLoader。可以使用以下命令进行安装:
$ npm install dataloader
然后,我们可以创建一个 DataLoader 的对象,这个对象将会在加载需要的数据时起到关键的作用。接着,我们要实现一个函数,这个函数需要从数据源中查询数据,以满足 GraphQL 查询。最后,我们要提供 GraphQL 查询语句,这个语句需要告诉 DataLoader 要加载那个函数以及对应的参数。
下面是一个示例代码:
// www.javascriptcn.com code example
const DataLoader = require('dataloader');
// 假定需要查询的数据结构如下
const authors = [{
id: '1',
name: 'Alice',
}, {
id: '2',
name: 'Bob',
}, {
id: '3',
name: 'Charlie',
}];
const books = [{
authorId: '1',
title: 'The Book of Alice',
}, {
authorId: '1',
title: 'The Second Book of Alice',
}, {
authorId: '2',
title: 'The Book of Bob',
}];
const authorLoader = new DataLoader(async (ids) => {
// 从数据源中查询数据
const result = ids.map(id => authors.find(a => a.id === id));
return result;
});
const bookLoader = new DataLoader(async (ids) => {
// 从数据源中查询数据
const result = ids.map(id => books.filter(b => b.authorId === id));
return result;
});
const resolvers = {
Query: {
authors: async (_, args, { authorLoader }) => {
return authorLoader.loadMany(args.ids);
},
books: async (_, args, { bookLoader }) => {
return bookLoader.loadMany(args.ids);
},
},
Author: {
books: async (author, _, { bookLoader }) => {
return bookLoader.load(author.id);
},
},
};
const typeDefs = `
type Author {
id: ID!
name: String!
books: [Book!]!
}
type Book {
title: String!
author: Author!
}
type Query {
authors(ids: [ID!]!): [Author!]!
books(ids: [ID!]!): [Book!]!
}
`;
const schema = makeExecutableSchema({ typeDefs, resolvers });在这个示例中,当 GraphQL 服务器接收到查询时,会调用相应查询的 DataLoader,该 DataLoader 将每个连接的对象的 ID 一起传递给它,并一次查询期望返回的对象。
在内部,我们通过使用 map 函数来过滤所需的对象。例如,在 bookLoader 中,从 books 数组中筛选出 id 数组中包含的对象。
在 resolvers 中,我们定义了将要处理的 GraphQL 查询语句,由于书籍与作者之间具有一对多的关系,我们在定义查询语句时注意到这一点,books 查询将返回一个作者的一组图书,因此我们使用 books(id: ID!) 定义查询格式。
使用 DataLoader,我们可以轻松地解决 N+1 查询问题,同时提高查询响应时间,避免多次发送查询请求。因此,DataLoader 在解决 GraphQL 查询中的性能问题方面发挥着关键的作用。
使用 dataloader-sequelize
对于使用 Sequelize ORM 的 Node.js 应用程序,datayloader-sequelize 是一种非常流行和实用的 N+1 查询解决办法。它是基于 DataLoader 的插件,可以帮助我们轻松地解决 N+1 查询问题。
首先,需要安装 dataloader-sequelize。可以使用以下命令进行安装:
$ npm install dataloader-sequelize
然后,我们可以创建一个 DataLoader 的对象来解决 N+1 查询问题。
下面是一个示例代码:
// www.javascriptcn.com code example
const { DataTypes, Sequelize } = require('sequelize');
const { Sequelize } = require('sequelize');
const { dataloaderSequelize } = require('dataloader-sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: ':memory:',
});
// 创建一个适用于 Sequelize 的 Data Loader 对象,并注册到 Sequelize 中
const authorLoader = dataloaderSequelize({
model: Author,
sequelize,
});
const bookLoader = dataloaderSequelize({
model: Book,
sequelize,
});
const Author = sequelize.define('author', {
name: { type: DataTypes.STRING },
});
const Book = sequelize.define('book', {
title: { type: DataTypes.STRING },
authorId: { type: DataTypes.INTEGER },
});
Book.belongsTo(Author, { foreignKey: 'authorId', as: 'author' });
const resolvers = {
Query: {
author: async (_, args, { authorLoader }) => {
return authorLoader.load(args.id);
},
book: async (_, args, { bookLoader }) => {
return bookLoader.load(args.id);
},
},
Book: {
author: async (book, _, { authorLoader }) => {
return authorLoader.load(book.authorId);
},
},
};
const typeDefs = `
type Author {
id: ID!
name: String!
books: [Book!]!
}
type Book {
id: ID!
title: String!
author: Author!
}
type Query {
author(id: ID!): Author!
book(id: ID!): Book!
}
`;
const schema = makeExecutableSchema({ typeDefs, resolvers });在这个示例中,我们定义了 authorLoader 和 bookLoader 两个 DataLoader 对象,在 resolvers 中使用这两个对象来查询所需数据。
此外,在 Author 和 Book 中,我们定义了模型之间的一对多关系,并为之定义了网络映射,取名为 books 和 author。我们在 Book 上使用 belongsTo 定义其所属关系,使用该方法可以方便的访问其相关关联对象数据。
在 Query 的中,我们定义了 author 和 book 的查询格式,并将 authorLoader 和 bookLoader 传递给了 author 和 book 的查询函数。
使用 dataloader-sequelize,我们可以避免发生 N+1 查询的问题,轻松地解决性能问题,以及更好地存储和操作数据。
小结
GraphQL 是一种优秀的前端技术,它可以帮助我们更快捷、高效地查询和获取后端数据。然而,在查询过程中, N+1 查询问题是一个非常严重的性能问题。本文提供了两种处理 N+1 查询问题的最佳实践,建议按照实际需求来选用 DataLoader 或 dataloader-sequelize。
在使用 GraphQL 进行查询时,除了考虑实现时间和查询结果以外,还要考虑性能问题,要使用一些方法来尽可能地减少查询次数,并减少网络延迟。这样可以更好的优化 GraphQL 查询性能。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d794bda941bf7134d8ed8f