投影与游标
include/exclude 与 cursors。
投影与游标
这一课你将能做到
- 用投影(projection)精确控制返回字段,包括内嵌字段与数组切片
- 说清楚游标(cursor)的工作机制,用 sort/skip/limit 做分页,并说出深分页的替代方案
- 理解 read preference 的直觉,知道什么业务可以读从节点
为什么要学
「查出整条文档再让 Java 挑字段」是新手最常见的浪费:一条提交记录带着完整代码和全部用例输出可能几十 KB,列表页只需要 slug 和分数,100 条就是几 MB 的无效网络传输。投影让 MongoDB 在服务端就把字段裁好。
游标则决定了你怎么「消费」查询结果:一次性 toArray 百万条会打爆内存;skip(100000) 翻页会越来越慢直到超时。这一课教你正确地遍历与分页。
先建立图像
查询执行的两个维度:
find( filter, projection )
│ │
选哪些文档 每个文档留哪些字段
(行裁剪) (列裁剪)
游标:服务端只把结果分批(batch)发给客户端
┌────────┐ batch 1 (101条) ┌────────┐
│ server │ ──────────────────> │ client │ it / hasNext() 再取下一批
└────────┘ <──── getMore ───── └────────┘
投影规则速查:
{ name: 1, score: 1 } 只返回 name、score(以及默认的 _id)
{ name: 1, _id: 0 } 只返回 name,去掉 _id
{ code: 0, cases: 0 } 排除 code 和 cases,其余全返回
{ "cases.name": 1 } 只返回数组元素的 name 子字段
{ cases: { $slice: 3 } } 只返回数组前 3 个元素
铁律:除 _id 外,include 和 exclude 不能混用。 {name: 1, code: 0} 会报错。
跟着做
准备数据:
use codeforge
db.submissions.drop()
db.submissions.insertMany([
{ userId: 1024, lessonSlug: "mdbrm-intro", status: "accepted", score: 100,
code: "db.x.find()", cases: [{name:"c1",passed:true},{name:"c2",passed:true},{name:"c3",passed:false}],
submittedAt: new Date("2026-10-01") },
{ userId: 1024, lessonSlug: "mdbrm-crud", status: "accepted", score: 85,
code: "db.x.updateOne()", cases: [{name:"c1",passed:true},{name:"c2",passed:false}],
submittedAt: new Date("2026-10-04") },
{ userId: 2048, lessonSlug: "mdbrm-intro", status: "failed", score: 40,
code: "db.x.remove()", cases: [{name:"c1",passed:false}],
submittedAt: new Date("2026-10-03") }
])
步骤 1:基础投影
列表页只要概要:
db.submissions.find(
{ userId: 1024 },
{ lessonSlug: 1, status: 1, score: 1, submittedAt: 1, _id: 0 }
)
步骤 2:内嵌字段与数组切片
只看用例名字和第一个失败用例:
db.submissions.find(
{ userId: 1024 },
{ lessonSlug: 1, "cases.name": 1, "cases.passed": 1 }
)
// 只要数组前 2 个元素
db.submissions.find(
{ userId: 1024 },
{ lessonSlug: 1, cases: { $slice: 2 } }
)
// $slice 也支持 [skip, limit]:跳过前 1 个取 2 个
db.submissions.find({}, { cases: { $slice: [1, 2] } })