$sort / $limit 是什么
聚合里的 $sort、$limit、$skip 负责排序与截断:$sort 按一个或多个字段排序(1 升序、-1 降序),$limit 取前 N 条,$skip 跳过前 N 条。$sort 紧跟 $limit 可以表达 Top-N,优化器会把「先全排序再截断」改写成只保留前 N 条的堆排序,内存占用显著下降;两者之间隔了其它阶段时这个改写不成立。排序阶段默认受 100MB 内存上限约束,超限直接报错。
基本信息
| Top-N 写法 | $sort 之后紧跟 $limit |
|---|---|
| 排序方向 | -1 降序,1 升序 |
| 内存上限 | 单阶段默认 100MB,超出报错 |
| 溢写开关 | allowDiskUse 允许溢写磁盘换取内存 |
| 分页 | $skip + $limit 只适合小偏移 |
| 索引 | 管道首段的排序与索引顺序一致时可免排序 |
典型使用场景
- 榜单查询:按金额、积分或数量取前 N 名
- 最新记录:按时间倒序取最近若干条事件
- 明细分页:小偏移翻页,或用排序键做键集分页
- 结果裁剪:聚合算完只返回需要展示的几行,减少网络传输
- 分布核对:分别取最大与最小的几条,确认数据是否符合预期
写法与优化
写法与优化
db.orders.aggregate([
{ $match: { status: "paid" } }, // 先过滤,减少参与排序的文档数
{ $sort: { ts: -1 } }, // 按时间倒序
{ $limit: 3 } // 与 $sort 相邻才会走堆排序优化
])
// 小偏移分页(偏移很大时改用排序键范围条件)
db.orders.aggregate([ { $sort: { ts: -1 } }, { $skip: 20 }, { $limit: 20 } ])
// 排序占用超 100MB 时放行磁盘溢写,用速度换内存
db.orders.aggregate([
{ $sort: { amount: -1 } },
{ $limit: 100 }
], { allowDiskUse: true })三个阶段对照
| 阶段 | 作用 | 代价 |
|---|---|---|
| $sort | 排序,可与索引顺序一致时免排序 | 内存占用,超 100MB 报错 |
| $limit | 取前 N 条 | 本身开销很小 |
| $skip | 跳过前 N 条 | 偏移越大越慢,前面的记录同样被处理 |
| $sort + $limit | Top-N,改写为堆排序 | 两者必须相邻 |
| allowDiskUse | 允许溢写磁盘 | 缓解内存但整体更慢 |
管道最前面的 $match + $sort + $limit 若能命中索引,服务端可以直接按索引顺序读前 N 条,几乎不产生额外排序开销,这是榜单类查询最理想的形态。
$skip 十万这类深分页会让每一步都白扫大量文档,延迟随页数线性上升;换成带排序键的范围条件($lt 上一页末值)才能保持稳定延迟。