投影是干什么的
投影是查询的第二个参数,决定返回文档里保留哪些字段:{field: 1} 为包含,{field: 0} 为排除,除 _id 外两者不能混用。它不只是"少显示几列",还直接影响磁盘回表、网络传输与客户端反序列化开销,并且是覆盖查询(covered query)成立的前提条件。
基本信息
| 包含写法 | { name: 1, email: 1 } |
|---|---|
| 排除写法 | { password: 0, apiToken: 0 } |
| _id 例外 | 可在包含模式里单独给 _id: 0 |
| 嵌套与数组 | { "addr.city": 1 }、{ items: { $slice: 3 } } |
| 生效位置 | find/findOne 第二参数;聚合里对应 $project 阶段 |
示例
示例
db.users.find({}, { name: 1, email: 1, _id: 0 }) # 只取需要的字段
db.users.find({}, { password: 0, apiToken: 0 }) # 排除敏感字段
db.users.find({ vip: true }, { name: 1, "addr.city": 1 }) # 嵌套字段用点号路径
db.orders.find({}, { items: { $slice: 3 } }) # 数组只取前 3 个元素
db.users.find({ role: "ops" }).projection({ name: 1, _id: 0 }) # 链式写法等价
db.users.find({}, { name: 1 }).explain("executionStats") # 确认是否走了覆盖查询典型使用场景
- 接口返回:只查前端真正用到的字段,省带宽也省序列化时间
- 安全兜底:默认排除 password、token 一类字段,避免日志或响应误带敏感数据
- 列表页:大文档用 $slice 截断数组,防止单条响应几百 KB 拖慢整页
- 覆盖查询:投影字段全部落在索引里并排除 _id,可实现不回表读取
- 导出归档:只导出必要字段,减小 dump 体积与恢复时间
- 排查数据问题:先小投影确认字段是否有值、类型是否与预期一致,再决定是否整篇取回
投影写法对照
| 写法 | 含义 | 注意 |
|---|---|---|
| {a:1} | 包含模式:只返回 a 与 _id | 包含模式里不能出现 0 字段 |
| {a:0} | 排除模式:返回除 a 外全部字段 | 宽文档依旧很大 |
| {_id:0} | 单独排除 _id | 覆盖查询必须带上 |
| {$slice:n} | 数组投影取前 n 个元素 | 仅对数组字段有效 |
覆盖查询成立还需要索引本身包含所有被查询与投影的字段;只投影了索引字段却漏掉 _id:0,执行计划仍会带 FETCH 阶段回表读文档,可用 explain 的 totalDocsExamined 是否为 0 来验证。
排除模式无法只取部分字段,宽文档传回客户端仍要付完整网络代价;接口类查询优先用包含模式做白名单。
投影只决定返回什么,不等于加密:用排除法"隐藏"的字段仍以明文存在集合里,且 profiler 与慢日志会记录原始查询语句与条件值,敏感数据该靠字段级加密或独立存储解决。