使用 Lean 加快 Mongoose 查询速度
¥Faster Mongoose Queries With Lean
精益选择 告诉 Mongoose 跳过 hydrating 结果文档。这使得查询速度更快,内存占用更少,但结果文档是普通的旧 JavaScript 对象(POJO),而不是 Mongoose 文档。在本教程中,你将了解有关使用 lean()
的权衡的更多信息。
¥The lean option tells Mongoose to skip
hydrating the result documents. This
makes queries faster and less memory intensive, but the result documents are
plain old JavaScript objects (POJOs), not Mongoose documents.
In this tutorial, you'll learn more about the tradeoffs of using lean()
.
使用精益
¥Using Lean
默认情况下,Mongoose 查询返回 Mongoose Document
级 的实例。文档比普通 JavaScript 对象重得多,因为它们有大量用于更改跟踪的内部状态。启用 lean
选项告诉 Mongoose 跳过实例化完整的 Mongoose 文档,只为你提供 POJO。
¥By default, Mongoose queries return an instance of the
Mongoose Document
class. Documents are much
heavier than vanilla JavaScript objects, because they have a lot of internal
state for change tracking. Enabling the lean
option tells Mongoose to skip
instantiating a full Mongoose document and just give you the 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; // approximately 55, about 3x smaller!
// In case you were wondering, the JSON form of a Mongoose doc is the same
// as the POJO. This additional memory only affects how much memory your
// Node.js process uses, not how much data is sent over the network.
JSON.stringify(normalDoc).length === JSON.stringify(leanDoc).length; // true
在后台,执行查询后,Mongoose 将查询结果从 POJO 转换为 Mongoose 文档。如果打开 lean
选项,Mongoose 会跳过此步骤。
¥Under the hood, after executing a query, Mongoose converts the query results
from POJOs to Mongoose documents. If you turn on the lean
option, Mongoose
skips this step.
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'
启用 lean
的缺点是精益文档没有:
¥The downside of enabling lean
is that lean docs don't have:
变更跟踪
¥Change tracking
铸造和验证
¥Casting and validation
获取器和设置器
¥Getters and setters
虚拟
¥Virtuals
save()
例如,以下代码示例显示,如果启用 lean
,Person
模型的 getter 和 virtual 不会运行。
¥For example, the following code sample shows that the Person
model's getters
and virtuals don't run if you enable lean
.
// 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
填充 与 lean()
配合使用。如果你同时使用 populate()
和 lean()
,则 lean
选项也会传播到填充的文档。在下面的示例中,顶层 '群组' 文档和填充的 '人' 文档都将是精简的。
¥Populate works with lean()
. If you
use both populate()
and lean()
, the lean
option propagates to the
populated documents as well. In the below example, both the top-level
'Group' documents and the populated 'Person' documents will be lean.
// 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
如果你正在执行查询并将结果不加修改地发送到 Express 响应 等设备,那么你应该使用 lean。一般来说,如果不修改查询结果,不使用 自定义获取器,就应该使用 lean()
。如果你修改查询结果或依赖 getter 或 transforms 等功能,则不应使用 lean()
。
¥If you're executing a query and sending the results without modification to,
say, an Express response, you should
use lean. In general, if you do not modify the query results and do not use
custom getters, you should use
lean()
. If you modify the query results or rely on features like getters
or transforms, you should not
use lean()
.
下面是 Express 路由 的示例,它是 lean()
的良好候选者。该路由不会修改 person
文档,也不依赖于任何 Mongoose 特定的功能。
¥Below is an example of an Express route
that is a good candidate for lean()
. This route does not modify the person
doc and doesn't rely on any Mongoose-specific functionality.
// 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 }));
});
以下是不应使用 lean()
的快速路由示例。根据一般经验,GET
路由是 RESTful API 中 lean()
的良好候选路由。另一方面,PUT
、POST
等路由一般不宜使用 lean()
。
¥Below is an example of an Express route that should not use lean()
. As
a general rule of thumb, GET
routes are good candidates for lean()
in a
RESTful API.
On the other hand, PUT
, POST
, etc. routes generally should not use 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 }));
});
请记住,虚拟值不会出现在 lean()
查询结果中。使用 Mongoose 精益虚拟插件 将虚拟添加到你的精益查询结果中。
¥Remember that virtuals do not end up in lean()
query results. Use the
mongoose-lean-virtuals plugin
to add virtuals to your lean query results.
插件
¥Plugins
使用 lean()
会绕过所有 Mongoose 功能,包括 virtuals、getters/setters 和 defaults。如果你想在 lean()
上使用这些功能,你需要使用相应的插件:
¥Using lean()
bypasses all Mongoose features, including virtuals, getters/setters,
and defaults. If you want to
use these features with lean()
, you need to use the corresponding plugin:
然而,你需要记住,Mongoose 不会水合精益文档,因此 this
将是虚拟、getter 和默认函数中的 POJO。
¥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.
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.
});
BigInts
默认情况下,MongoDB Node 驱动程序将 MongoDB 中存储的长整型转换为 JavaScript 数字,而不是 BigInts。在 lean()
查询上设置 useBigInt64
选项,将 long 膨胀为 BigInt。
¥By default, the MongoDB Node driver converts longs stored in MongoDB into JavaScript numbers, not BigInts.
Set the useBigInt64
option on your lean()
queries to inflate longs into BigInts.
const Person = mongoose.model('Person', new mongoose.Schema({
name: String,
age: BigInt
}));
// Mongoose will convert `age` to a BigInt
const { age } = await Person.create({ name: 'Benjamin Sisko', age: 37 });
typeof age; // 'bigint'
// By default, if you store a document with a BigInt property in MongoDB and you
// load the document with `lean()`, the BigInt property will be a number
let person = await Person.findOne({ name: 'Benjamin Sisko' }).lean();
typeof person.age; // 'number'
// Set the `useBigInt64` option to opt in to converting MongoDB longs to BigInts.
person = await Person.findOne({ name: 'Benjamin Sisko' }).
setOptions({ useBigInt64: true }).
lean();
typeof person.age; // 'bigint'