lean()大幅提升查询速度,因它跳过Mongoose Document实例化,直接返回轻量POJO,避免getter/setter、变更追踪等开销,降低内存占用、序列化耗时和GC压力。

lean() 为什么能让查询快很多
因为 Mongoose 默认返回的是带完整响应式逻辑的 Document 实例,不是普通对象。每个实例都内置 getter/setter、变更追踪、虚拟字段、验证钩子、toObject() 方法等——查 1000 条,就得创建 1000 个“重型对象”,内存占用高、序列化慢、GC 压力大。而 lean() 直接跳过这一整套封装过程,数据库查出 BSON 数据后,只做最轻量的 JSON 解析,返回纯 JavaScript 对象(POJO),没有方法、没有隐藏属性、不监听变更。
lean() 必须在 exec() 或 await 前调用
lean() 是查询构造阶段的选项,不是对结果的后期转换。它必须出现在 find()、findOne() 等之后、exec() 或 await 之前。
- ✅ 正确:
Item.find({ status: "active" }).lean().exec()或await Item.find({ status: "active" }).lean() - ❌ 错误:
await Item.find({}).exec().lean()(报TypeError: exec().lean is not a function) - ❌ 错误:
const docs = await Item.find({}); docs[0].toObject().lean()(.toObject()不是.lean(),且已晚)
lean() 后的对象不能调用 Document 方法
启用 lean() 后,返回值是普通对象,没有 .save()、.validate()、.toJSON()、.toObject() 等方法。如果你需要:
- 保留虚拟字段(如
fullName):加.lean({ virtuals: true }) - 保留自定义
toObject行为(比如transform):得手动实现,或改用非 lean 查询 +.toObject({ getters: true, virtuals: true }) - 后续还要更新这条数据:就不能用
lean(),得另起一次非 lean 查询获取可操作的Document
lean() 不解决“查多少条才合理”这个问题
很多人以为加了 lean() 就能随便查几万条,其实不然。它只降低单次查询的内存和序列化开销,但不改变网络传输量、不减少磁盘 I/O、也不绕过 MongoDB 的扫描成本。如果一次查 5 万条,哪怕全是 POJO,Node.js 进程照样可能 OOM,前端也收不到响应。真正要压测的不是“加没加 lean()”,而是“这个接口到底该不该一次性返回这么多”。分页、流式读取、聚合管道裁剪字段,这些才是和 lean() 配合使用的必要手段。











