Faster Mongoose Queries With Lean使用精益实现更快的Mongoose查询

The lean option tells Mongoose to skip hydrating the result documents. lean选项告诉Mongoose跳过补水结果文档。This makes queries faster and less memory intensive, but the result documents are plain old JavaScript objects (POJOs), not Mongoose documents. 这使得查询速度更快,内存占用更少,但结果文档是普通的旧JavaScript对象(POJO),而不是Mongoose文档In this tutorial, you'll learn more about the tradeoffs of using lean().在本教程中,您将了解更多关于使用lean()的权衡。

Using 使用Lean

By default, Mongoose queries return an instance of the Mongoose Document class.默认情况下,Mongoose查询返回Mongoose Document类的一个实例。Documents are much heavier than vanilla JavaScript objects, because they have a lot of internal state for change tracking. 文档比普通的JavaScript对象要重得多,因为它们有很多用于更改跟踪的内部状态。Enabling the lean option tells Mongoose to skip instantiating a full Mongoose document and just give you the POJO.启用lean选项告诉Mongoose跳过实例化完整的Mongooser文档,只需提供POJO。

const leanDoc = await MyModel.findOne().lean();

How much smaller are lean documents? Here's a comparison.精简文档要小多少?这是一个比较。

const schema = new mongoose.Schema({ name: String });
const MyModel = mongoose.model('Test', schema);

await MyModel.create({ name: 'test' });

const normalDoc = await MyModel.findOne();
// To enable the `lean` option for a query, use the `lean()` function.
const leanDoc = await MyModel.findOne().lean();

v8Serialize(normalDoc).length; // approximately 180
v8Serialize(leanDoc).length; // 32, about 5x smaller!

// In case you were wondering, the JSON form of a Mongoose doc is the same as the POJO. 如果您想知道,Mongoose文档的JSON形式与POJO相同。This additional memory only affects how much memory your这个额外的内存只会影响你的内存
// Node.js process uses, not how much data is sent over the network.Node.js进程使用,而不是通过网络发送多少数据。
JSON.stringify(normalDoc).length === JSON.stringify(leanDoc).length; // true

Under the hood, after executing a query, Mongoose converts the query results from POJOs to Mongoose documents. 在后台,在执行查询后,Mongoose将POJO的查询结果转换为Mongoose文档。If you turn on the lean option, Mongoose skips this step.如果你打开lean选项,Mongoose会跳过这一步。

const normalDoc = await MyModel.findOne();
const leanDoc = await MyModel.findOne().lean();

normalDoc instanceof mongoose.Document; // true
normalDoc.constructor.name; // 'model'

leanDoc instanceof mongoose.Document; // false
leanDoc.constructor.name; // 'Object'

The downside of enabling lean is that lean docs don't have:启用lean的缺点是精益文档没有:

  • Change tracking更改跟踪
  • Casting and validation铸造和验证
  • Getters and setterssetter和setter
  • Virtuals虚拟机
  • save()

For example, the following code sample shows that the Person model's getters and virtuals don't run if you enable lean.例如,下面的代码示例显示,如果启用leanPerson模型的getter和virtual就不会运行。

// Define a `Person` model. Schema has 2 custom getters and a `fullName`
// virtual. Neither the getters nor the virtuals will run if lean is enabled.
const personSchema = new mongoose.Schema({
firstName: {
type: String,
get: capitalizeFirstLetter
},
lastName: {
type: String,
get: capitalizeFirstLetter
}
});
personSchema.virtual('fullName').get(function() {
return `${this.firstName} ${this.lastName}`;
});
function capitalizeFirstLetter(v) {
// Convert 'bob' -> 'Bob'
return v.charAt(0).toUpperCase() + v.substring(1);
}
const Person = mongoose.model('Person', personSchema);

// Create a doc and load it as a lean doc
await Person.create({ firstName: 'benjamin', lastName: 'sisko' });
const normalDoc = await Person.findOne();
const leanDoc = await Person.findOne().lean();

normalDoc.fullName; // 'Benjamin Sisko'
normalDoc.firstName; // 'Benjamin', because of `capitalizeFirstLetter()`
normalDoc.lastName; // 'Sisko', because of `capitalizeFirstLetter()`

leanDoc.fullName; // undefined
leanDoc.firstName; // 'benjamin', custom getter doesn't run
leanDoc.lastName; // 'sisko', custom getter doesn't run

Lean and Populate精益和大众化

Populate works with lean(). Populate使用lean()进行工作。If you use both populate() and lean(), the lean option propagates to the populated documents as well. 如果同时使用populate()lean(),则lean选项也会传播到已填充的文档。In the below example, both the top-level 'Group' documents and the populated 'Person' documents will be lean.在下面的示例中,顶层的“Group”文档和填充的“Person”文档都将是精简的。

// Create models
const Group = mongoose.model('Group', new mongoose.Schema({
name: String,
members: [{ type: mongoose.ObjectId, ref: 'Person' }]
}));
const Person = mongoose.model('Person', new mongoose.Schema({
name: String
}));

// Initialize data
const people = await Person.create([
{ name: 'Benjamin Sisko' },
{ name: 'Kira Nerys' }
]);
await Group.create({
name: 'Star Trek: Deep Space Nine Characters',
members: people.map(p => p._id)
});

// Execute a lean query
const group = await Group.findOne().lean().populate('members');
group.members[0].name; // 'Benjamin Sisko'
group.members[1].name; // 'Kira Nerys'

// Both the `group` and the populated `members` are lean.
group instanceof mongoose.Document; // false
group.members[0] instanceof mongoose.Document; // false
group.members[1] instanceof mongoose.Document; // false

Virtual populate虚拟填充 also works with lean.也适用于精益。

// Create models
const groupSchema = new mongoose.Schema({ name: String });
groupSchema.virtual('members', {
ref: 'Person',
localField: '_id',
foreignField: 'groupId'
});
const Group = mongoose.model('Group', groupSchema);
const Person = mongoose.model('Person', new mongoose.Schema({
name: String,
groupId: mongoose.ObjectId
}));

// Initialize data
const g = await Group.create({ name: 'DS9 Characters' });
await Person.create([
{ name: 'Benjamin Sisko', groupId: g._id },
{ name: 'Kira Nerys', groupId: g._id }
]);

// Execute a lean query
const group = await Group.findOne().lean().populate({
path: 'members',
options: { sort: { name: 1 } }
});
group.members[0].name; // 'Benjamin Sisko'
group.members[1].name; // 'Kira Nerys'

// Both the `group` and the populated `members` are lean.
group instanceof mongoose.Document; // false
group.members[0] instanceof mongoose.Document; // false
group.members[1] instanceof mongoose.Document; // false

When to Use Lean何时使用Lean

If you're executing a query and sending the results without modification to, say, an Express response, you should use lean. 如果您正在执行查询并在不修改的情况下将结果发送到Express响应,那么您应该使用lean。In general, if you do not modify the query results and do not use custom getters, you should use lean(). 通常,如果您不修改查询结果,也不使用自定义getter,那么应该使用lean()If you modify the query results or rely on features like getters or transforms, you should not use lean().如果修改查询结果或依赖getter或transforms等功能,则不应使用lean()

Below is an example of an Express route that is a good candidate for lean(). 下面是一个快速路线的例子,它是lean()的一个很好的候选者。This route does not modify the person doc and doesn't rely on any Mongoose-specific functionality.这条路线不修改person文档,也不依赖于任何Mongoose特定的功能。

// As long as you don't need any of the Person model's virtuals or getters,
// you can use `lean()`.
app.get('/person/:id', function(req, res) {
Person.findOne({ _id: req.params.id }).lean().
then(person => res.json({ person })).
catch(error => res.json({ error: error.message }));
});

Below is an example of an Express route that should not use lean(). 下面是一个不应该使用lean()的Express路由示例。As a general rule of thumb, GET routes are good candidates for lean() in a RESTful API. 一般来说,GET路由是RESTful APIlean()的理想候选。On the other hand, PUT, POST, etc. routes generally should not use lean().另一方面,PUTPOST等路由通常不应该使用lean()

// This route should **not** use `lean()`, because lean means no `save()`.
app.put('/person/:id', function(req, res) {
Person.findOne({ _id: req.params.id }).
then(person => {
assert.ok(person);
Object.assign(person, req.body);
return person.save();
}).
then(person => res.json({ person })).
catch(error => res.json({ error: error.message }));
});

Remember that virtuals do not end up in lean() query results. 请记住,虚拟机最终不会出现在lean()查询结果中。Use the mongoose-lean-virtuals plugin to add virtuals to your lean query results.使用mongoose精益虚拟机插件将虚拟机添加到精益查询结果中。

Plugins插件

Using lean() bypasses all Mongoose features, including virtuals, getters/setters, and defaults. 使用lean()可以绕过Mongoose的所有特性,包括虚拟特性、getters/ssetter默认特性。If you want to use these features with lean(), you need to use the corresponding plugin:如果你想在lean()中使用这些功能,你需要使用相应的插件:

However, you need to keep in mind that Mongoose does not hydrate lean documents, so this will be a POJO in virtuals, getters, and default functions.然而,您需要记住,Mongoose不会对精简文档进行水合,因此this将是虚拟、getter和默认函数中的POJO。

const schema = new Schema({ name: String });
schema.plugin(require('mongoose-lean-virtuals'));

schema.virtual('lowercase', function() {
this instanceof mongoose.Document; // false

this.name; // Works
this.get('name'); // Crashes because `this` is not a Mongoose document.
});