MongoDB大量查询变慢主因是Mongoose默认返回带响应式逻辑的Document实例,导致内存高、序列化慢、GC压力大;启用lean()可返回POJO提升性能,但须在exec前调用且不可再调用Document方法。
默认情况下,Mongoose 查询返回的是 Mongoose Document 实例,不是普通对象。每个实例都带有一整套响应式逻辑:变更追踪、getter/setter、验证钩子、虚拟属性支持等。当一次查出几千条记录时,这些开销会指数级放大——内存占用高、序列化慢、GC 压力大,最终拖慢整个响应。
.lean() 是一个查询选项,必须写在 find()、findOne()、findById() 等方法之后、.exec() 或 await 之前。它不是对已返回结果的“转换”,而是在查询执行前就告诉 Mongoose:“别封装成 Document,直接给我 POJO”。
Item.find({ status: "active" }).lean().exec() 或 await Item.find({ status: "active" }).lean()
await Item.find({ status: "active" }).exec().lean()(报错:TypeError: exec(...).lean is not a function)const docs = await Item.find({}); docs.map(d => d.toObject().lean())(.toObject() 不是 .lean(),且已晚)启用 .lean() 后,返回值是纯 Object,没有 .save()、.validate()、.toObject()、.toJSON() 等方法。如果你需要:
fullName),加 .lean({ virtuals: true })
toObject() 行为(比如自定义 transform),得手动实现或改用 .toObject({ getters: true, virtuals: true }) 配合非 lean 查询.lean(),得另起一次非 lean 查询来获取可操作的 Document查购物车列表并拼商品详情这类场景,别用 for...of + await getItemById(id) 串行查——哪怕每个都 .lean(),总耗时仍是线性叠加。正确做法是:
cartItems.map(i => i.itemId) 提取所有 IDItem.find({ _id: { $in: itemIds } }).lean() 拿到全部商品new Map(items.map(i => [i._id.toString(), i])) 构建 ID → item 映射cartItems,从 Map 中取值并直接挂载 count、filter 字段这个组合把 N 次查询压成 1 次,.lean() 减少单次返回开销,Map 查找是 O(1),三者叠加效果远超单独用 .lean()。
最容易被忽略的一点:.lean() 只解决“返回对象能不能改”的问题,不解决“查多少条才合理”。如果接口本该分页却一次性拉 10 万条,加了 .lean() 也救不回内存和网络传输瓶颈——该加 limit、该分页、该加索引的地方,一个都不能少。