lean查询能减少内存占用,因为mongoose默认返回带方法和追踪逻辑的model实例,而lean()跳过实例化,直接返回纯javascript对象,批量查询时内存可降40%–70%。

Lean查询为什么能减少内存占用
Mongoose默认返回的是Model实例,每个文档都包裹着大量内部方法、验证逻辑、变更追踪(如 isModified、save)、getter/setter 和原型链。即使你只读取数据,这些开销也照常存在。启用 lean() 后,Mongoose 直接返回普通 JavaScript 对象(plain object),跳过实例化过程,内存占用通常能下降 40%–70%,尤其在批量查询(如 find() 返回数百上千条)时效果明显。
什么时候必须加 lean() 才有效
lean() 只对查询执行函数生效,且必须在 exec() 或 then() 前调用。它不会作用于已执行的查询,也不能补加。
- ✅ 正确:
MyModel.find({ status: "active" }).lean().exec() - ✅ 正确(Promise 风格):
MyModel.find({ status: "active" }).lean().then(docs => {...}) - ❌ 无效:
MyModel.find({ status: "active" }).exec().lean()(exec()已返回 Model 实例) - ❌ 无效:
const docs = await MyModel.find(...); docs.lean()(语法错误,docs是数组,没有lean方法)
用了 lean() 之后不能干哪些事
因为返回的是纯对象,所有 Mongoose 文档实例的方法和特性全部消失:
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
- 不能再调用
doc.save()、doc.remove()、doc.populate() - 无法使用虚拟字段(
virtuals),除非手动在 schema 中开启toJSON: { virtuals: true }并配合lean({ virtuals: true })(仅 v7.0+ 支持) - 日期字段仍是
Date对象,但doc.createdAt.toISOString()这类操作仍可工作;而doc.isNew、doc.$errors等属性直接undefined - 如果你依赖
toObject()或toJSON()的定制行为(比如过滤敏感字段),需改用JSON.parse(JSON.stringify(doc))或手动映射
性能差异实测建议与边界情况
别只看文档说“变快了”,真实影响取决于你的数据结构和查询模式:
- 单条小文档(
- 嵌套深、含大量 ref 的文档:即使加了
lean(),若后续还调用populate(),则populate本身仍会创建子文档实例 —— 此时应改用lean({ virtuals: false, defaults: false })+ 手动 join,或用aggregate()替代 - Node.js 堆内存告警(
FATAL ERROR: Reached heap limit)时,优先检查是否漏了lean(),尤其是分页接口中未限制limit却又没加lean() - v6.8+ 开始支持
lean({ defaults: false })(跳过默认值填充),进一步减小对象体积;v7.0+ 支持{ virtuals: true },但注意这会轻微增加序列化开销
真正容易被忽略的是:Lean 不是银弹。如果业务层已经对 Model 实例做了深度依赖(比如封装了 doc.formatName() 这样的方法),强行加 lean() 会导致运行时报错,而不是启动报错 —— 这类问题往往在线上批量请求时才暴露。










