Nodejs mongoose的Population问题

Nodejs mongoose的Population问题

Population应该尽量少用吗?我在nodeclub的models中没看到使用ref来做关联,那读取数据的时候怎么做到一次查询

6 回复

Nodejs Mongoose 的 Population 问题

问题背景: 在使用 Mongoose 进行开发时,你可能会遇到如何高效地处理文档之间的引用(references)的问题。Mongoose 提供了一个强大的功能叫做 “Population”,它允许你在查询时自动填充引用的文档。但是,有人提出是否应尽量少用 Population,因为 Nodeclub 的模型中没有看到使用 ref 来做关联。

问题核心: 如何在不频繁使用 Population 的情况下,实现高效的数据查询?

解决方案:

  1. 使用 Population:

    • 优点: 简化代码,提高可读性。
    • 缺点: 可能会导致性能问题,特别是在引用层级较深或引用数量较多的情况下。
  2. 手动处理引用:

    • 优点: 更灵活,可以更好地控制查询性能。
    • 缺点: 代码复杂度增加。

示例代码

使用 Population

假设我们有两个模型:UserPost,其中每个 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

mongoosepopulation 是一种将引用字段(通过 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 通常是最简单的方法。

回到顶部