$push 本身不保证顺序,需配合 $each 和内嵌 $sort 实现分组内排序;大数据量时应启用 allowDiskUse 或前置 $limit 避免内存溢出。

聚合中 $push 后排序失效,是因为它不保证顺序
你写 $push 想把匹配文档按时间加进数组,结果发现数组里顺序乱了——这不是 bug,是设计如此。$push 本身不承诺插入顺序,尤其在分片集群或涉及多阶段管道时,上游 $sort 的效果不会自动延续到 $push 的输出里。
真正起作用的是 $push 的 $each + $sort 组合,或者改用 $addToSet(但会去重,不适用多数场景)。
- 必须显式用
$sort阶段排好序,再进$group,且$push要配合$each和内嵌$sort - 错误写法:
{$push: "$field"}—— 完全不控制顺序 - 正确写法:
{$push: {$each: ["$field"], $sort: {"timestamp": -1}}},注意$sort是对象,不是字符串 - 如果字段是嵌套的(比如
"log.time"),$sort键名要写全,不能简写
$push 大量数据触发内存限制(errmsg: "Sort exceeded memory limit")
当你要 $push 几千条记录进一个数组,MongoDB 默认 100MB 内存上限会立刻打满。这不是因为 $push 本身耗内存,而是它前面的 $sort 或 $group 在内存里建临时结构,数据一多就崩。
关键不是“关掉内存限制”,而是让排序/分组不全量加载。
- 优先加
allowDiskUse: true到db.collection.aggregate()调用里,这是最直接的绕过方式 - 但更治本的是:在
$group前加$limit,比如只取最新 100 条再$push,避免无意义堆积 - 如果业务允许,把排序逻辑下推到应用层:先用
$group+$push收集 ID 列表,再用find({_id: {$in: [...]}})查详情并排序 - 注意
allowDiskUse在事务里不可用,若在事务中聚合,必须提前规划数据量
用 $push 实现“按字段值分组后各自排序”
比如用户有多个订单,你想每个用户自己的订单按金额降序排进数组——这不是全局排序,而是“分组内排序”。这时候不能只靠顶层 $sort,得用 $push 的内嵌 $sort。
示例场景:按 userId 分组,每个用户的 orders 数组按 amount 降序:
db.orders.aggregate([
{$sort: {userId: 1, amount: -1}},
{$group: {
_id: "$userId",
orders: {$push: {$each: ["$orderData"], $sort: {amount: -1}}}
}}
])
-
$sort必须在$group前,否则$push的$sort不生效 -
$each参数必须是数组(哪怕只推一个字段,也要写["$orderData"]) - 如果字段名含点号(如
"address.city"),$sort键名同样要带点,MongoDB 不支持别名 - 这个写法在 4.4+ 才支持,4.2 及以下只能靠两次聚合或应用层处理
聚合结果排序和应用层排序的取舍
不是所有排序都该塞进聚合管道。当 $push 后还要对整个数组做复杂操作(比如按某子字段过滤、翻页、动态权重排序),聚合反而变重、难调试、难复用。
- 纯时间/数值顺序、固定规则 → 用聚合内
$push+$sort,一次到位 - 需要分页(比如只看第 3–5 条)、带搜索条件、或排序逻辑随请求变化 → 先用
$push收集 ID 或轻量字段,排序交给应用层 - 注意:Node.js 的
Array.sort()是不稳定排序,如果原始顺序重要(比如同金额订单要保持插入先后),得手动加索引字段保序 - MongoDB 6.0+ 的
$function能写 JS 排序逻辑,但性能差、无法索引、生产环境慎用
$sort 的位置和 $push 的写法是否严格匹配,而不是参数调优。内存报错时先看有没有漏 allowDiskUse,再确认是不是真需要把全部数据塞进一个数组。

















