Nodejs mongoose的Population问题
Nodejs mongoose的Population问题
Population应该尽量少用吗?我在nodeclub的models中没看到使用ref来做关联,那读取数据的时候怎么做到一次查询
Nodejs Mongoose 的 Population 问题
问题背景:
在使用 Mongoose 进行开发时,你可能会遇到如何高效地处理文档之间的引用(references)的问题。Mongoose 提供了一个强大的功能叫做 “Population”,它允许你在查询时自动填充引用的文档。但是,有人提出是否应尽量少用 Population,因为 Nodeclub 的模型中没有看到使用 ref
来做关联。
问题核心: 如何在不频繁使用 Population 的情况下,实现高效的数据查询?
解决方案:
-
使用 Population:
- 优点: 简化代码,提高可读性。
- 缺点: 可能会导致性能问题,特别是在引用层级较深或引用数量较多的情况下。
-
手动处理引用:
- 优点: 更灵活,可以更好地控制查询性能。
- 缺点: 代码复杂度增加。
示例代码
使用 Population
假设我们有两个模型:User
和 Post
,其中每个 Post
都有一个 author
字段,指向一个 User
文档。
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// User Schema
const userSchema = new Schema({
name: String,
});
// Post Schema
const postSchema = new Schema({
title: String,
content: String,
author: { type: Schema.Types.ObjectId, ref: 'User' },
});
const User = mongoose.model('User', userSchema);
const Post = mongoose.model('Post', postSchema);
// 查询所有文章并填充作者信息
Post.find()
.populate('author')
.then(posts => {
console.log(posts);
});
手动处理引用
如果你担心性能问题,可以选择手动处理引用。
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// User Schema
const userSchema = new Schema({
name: String,
});
// Post Schema
const postSchema = new Schema({
title: String,
content: String,
authorId: { type: Schema.Types.ObjectId, ref: 'User' },
});
const User = mongoose.model('User', userSchema);
const Post = mongoose.model('Post', postSchema);
// 查询所有文章并手动获取作者信息
Post.find()
.then(posts => {
return Promise.all(
posts.map(post =>
User.findById(post.authorId).then(author => ({ ...post.toObject(), author }))
)
);
})
.then(posts => {
console.log(posts);
});
总结
- 使用 Population: 简化代码,但要注意性能问题。
- 手动处理引用: 更灵活,但代码复杂度增加。
根据具体需求选择合适的方法。如果引用关系简单且数据量不大,使用 Population 是一个不错的选择。如果引用关系复杂或数据量较大,建议手动处理引用以优化性能。
如果是多表查询的话是不行的,具体要在程序里面实现。 Population貌似必须2个表之间有_id关联,可是很多情况貌似满足不了需求。 所以要么在存表的时候给一定的冗余,要么所有的关联在代码中实现(注意一下效率,避免内存溢出)
个人理解,不对勿喷
个人感觉是mongo数据库的集合设计问题 - 内嵌文档 vs. DBRef。如果有两个实体,比如User和Team,约束为M:1(一个Team下可以包含多个User,一个User只能属于一个Team),这种情况下,可以设计为DBRef,集合中数据可能为: User:
{ "_id" : ObjectId("id_of_user#1"), "teamId" : [ { "$ref" : "Team", "$id" : ObjectId("id_of_team#1") } ]}
{ "_id" : ObjectId("id_of_user#2"), "teamId" : [ { "$ref" : "Team", "$id" : ObjectId("id_of_team#1") } ]}
Team:
{ "_id" : ObjectId("id_of_team#1") }
在mongoose中,要定义上述关系,可以这样来定义Schema:
var User = new Schema({
team: { type: Schema.Types.ObjectId, ref: 'Team' },
});
当然还需要导出Schema为Model (mongoose.model(‘User’, User);)。定义Team:
var Team = new Schema({
team: { type: Schema.Types.ObjectId, ref: 'Team' },
});
从数据库里查询User时可以一并查出Team对象:
User.findOne('id_of_some_user')
.populate('team')
.exec(function (err, user) {
// ...
});
这样,user.team就不会是一个ObjectId对象。需要说明的是: 1). 查看数据库会发现,mongoose弄出来的数据和mongodb的官方做法不一样,比如它不是
{ "_id" : ObjectId("id_of_user#1"), "teamId" : [ { "$ref" : "Team", "$id" : ObjectId("id_of_team#1") } ]}
而是
{ "_id" : ObjectId("id_of_user#1"), "teamId" : ObjectId("id_of_team#1") }
这是因为通过Schema的定义,数据的关联关系已经表达得很清楚了。当然,这也可能成为你考虑是否使用mongoose的一个因素(数据活得比应用久,说不定你要考虑换框架呢)。
2). populate会向mongodb数据库发起新的数据库查询,即额外的查询请求(mongodb中并没有关系型数据库的表JOIN操作)。
不用populate 自己又如何写呢?难道拿到id再查询一次数据库,那结果不是还一样,如果是查询很多数据,要全部遍历一次分别拿id去查一下吗?我只是想知道大家不用populate 怎么处理的 还有populate 这个好像只能查询直接关联的model,关联的model的关联model就不能查询出来了,怎么办呢?
找到了,最新版的mongoose支持了deep-populate 官方文档: deep-populate
mongoose
的 population
是一种将引用字段(通过 ref
定义)转换为完整的文档对象的功能。虽然有些人可能会建议尽量减少使用 population
,因为每次进行 population 操作时都会发起额外的数据库查询,但这并不意味着你应该完全避免使用它。
在 Node.js 中,当你需要获取包含引用关系的数据时,population
是非常方便的工具。你可以根据具体的需求选择合适的方式来优化性能,比如预加载或者手动查询。
下面是一个简单的示例,说明如何在 Mongoose 中使用 population
:
示例模型定义
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// User Schema
const userSchema = new Schema({
name: { type: String, required: true }
});
// Post Schema
const postSchema = new Schema({
title: { type: String, required: true },
author: { type: Schema.Types.ObjectId, ref: 'User' } // 引用 User 模型
});
const User = mongoose.model('User', userSchema);
const Post = mongoose.model('Post', postSchema);
查询并 Population
async function findPostsAndPopulateAuthors() {
const posts = await Post.find().populate('author'); // 使用 populate 方法填充作者信息
console.log(posts);
}
findPostsAndPopulateAuthors();
预加载(推荐用于大批量数据)
如果预期会有大量的数据需要处理,可以考虑使用预加载来减少查询次数:
async function findPostsAndPreloadAuthors() {
const posts = await Post.find().populate({
path: 'author',
options: { lean: true } // 可选,减少内存使用
});
console.log(posts);
}
findPostsAndPreloadAuthors();
手动查询(不推荐用于大量数据)
如果你担心性能问题,可以选择手动查询引用的数据:
async function findPostsManual() {
const posts = await Post.find();
const authorIds = posts.map(post => post.author);
const authors = await User.find({ _id: { $in: authorIds } });
const authorsMap = authors.reduce((acc, author) => {
acc[author._id] = author;
return acc;
}, {});
posts.forEach(post => {
post.author = authorsMap[post.author];
});
console.log(posts);
}
findPostsManual();
总结:population
确实会带来额外的查询开销,但这是为了简化开发工作。你可以根据具体情况选择最合适的实现方式。对于小规模数据或开发阶段,直接使用 population
通常是最简单的方法。