游标与翻页
find() 返回的不是数组而是游标:服务端按批取数据,客户端按需读取。sort / skip / limit 挂在游标尾部,写法像客户端排序,实际由服务端执行(mongos 会把排序下推到分片)。排序本身是内存动作,命中索引才能免排序成本。
基本信息
| 适用版本 | MongoDB 4.x-8.x / mongosh |
|---|---|
| 语法 | db.<集合>.find(条件).sort({字段:1|-1}).skip(n).limit(m) |
| 默认输出 | mongosh 只打印前 20 条 |
| 排序内存上限 | 100MB,超出报错 |
| 所需权限 | read |
典型使用场景
- 列表页按时间倒序取最新 N 条:sort({ts:-1}).limit(20),最常用的组合
- 后台跳页浏览:skip+limit,页码不大时够用且写法简单
- 全量导出:用 hasNext/next 分批读,避免 toArray() 把结果全塞进内存
- 配合 projection 只取需要的字段再排序:减少网络传输与反序列化开销
- 分片集群按非分片键排序:需要 mongos 归并各分片结果,要评估代价
常用写法
常用写法
db.orders.find({ status: "paid" }, { orderNo: 1, ts: 1 }) # 条件 + 只投影两列
.sort({ ts: -1, _id: -1 }) # 时间倒序,_id 兜底让同秒记录顺序稳定
.limit(20) # 只返回 20 条
const c = db.orders.find().sort({ ts: -1 }).skip(100).limit(10) # 每页 10 条,取第 11 页
c.toArray() # 一次读完(数据量大时慎用)
c.hasNext() # 判断是否还有下一条
it # mongosh 中继续打印下一批游标方法对照
| 方法 | 作用 | 注意点 |
|---|---|---|
| sort({f:1}) | 排序,1 升序 -1 降序,多字段按书写顺序比较 | 未命中索引时走内存排序,超 100MB 报错 |
| limit(n) | 限制返回条数 | limit(0) 表示不限制,不是返回 0 条 |
| skip(n) | 跳过前 n 条 | 服务端仍要扫描并丢弃这 n 条,越翻越慢 |
| batchSize(n) | 设置单批返回条数 | 只影响网络分批,不改变结果集与顺序 |
| hasNext / next | 逐条取数 | 游标闲置默认 10 分钟超时失效 |
键集翻页(推荐)
键集翻页(推荐)
const p1 = db.orders.find({ status: "paid" }) # 第一页
.sort({ ts: -1, _id: -1 }).limit(20).toArray()
const last = p1[p1.length - 1] # 记住本页最后一条,作为下一页的起点
db.orders.find({ status: "paid", # 下一页:不用 skip,翻页开销恒定
$or: [ { ts: { $lt: last.ts } },
{ ts: last.ts, _id: { $lt: last._id } } ] })
.sort({ ts: -1, _id: -1 }).limit(20) # 数据增删也不会像 skip 那样错页排序未命中索引且数据量超过 100MB 时查询直接失败(QueryExceededMemoryLimit);正解是建匹配排序顺序的复合索引,allowDiskUse 只是兜底(MongoDB 4.4 起 find 也支持该选项)。
mongosh 默认打印前 20 条,输入 it 打印下一批;官方驱动不会自动打印,返回的是游标对象,必须自己 for await 或 toArray 消费,两者的“翻页”手感不同。
带条件的排序尽量让索引前缀覆盖过滤字段,例如查询 status+ts 排序,建 {status:1, ts:-1} 复合索引可同时付过滤与排序。