$sort 必须紧贴 $match 后,否则排名不可靠;正确顺序为[$match, $sort, $limit]或[$match, $group, $sort, $limit],且$sort字段需为$group输出字段、多字段排序须加唯一键、字符串数字排序需配collation、$sort字段必须有匹配方向的索引。

$sort 必须紧贴 $match 后,否则排名结果不可靠
聚合管道里做排名(比如销量 Top 10、用户积分榜),本质是先筛选再排序再截断。但很多人把 $sort 放在 $match 前面,或夹在 $group 和 $project 中间,导致结果每次都不一致,甚至漏掉高分项。
- 错误做法:
[$sort, $match, $limit]—— 先全表排序再过滤,内存爆、速度慢、结果错 - 正确顺序:
[$match, $sort, $limit]或[$match, $group, $sort, $limit] - 如果
$group后要排序,确保$sort字段是$group输出的字段,比如{_id: "$category", total: {$sum: "$amount"}},那排序就得用{total: -1},不能写{amount: -1}
多字段排序必须补唯一键,否则排名不稳定
比如按 status 升序、score 降序排用户,但大量用户 status: "active",仅靠 score 排序无法保证两次查询返回顺序一致 —— MongoDB 不承诺等值键文档的相对位置。
- 稳定写法:
{$sort: {status: 1, score: -1, _id: 1}} - 更推荐用业务唯一字段替代
_id,比如user_id或order_no,语义清晰且便于调试 - 别写重复字段,如
{score: 1, score: -1}会直接报errmsg: "Duplicate sort key"
字符串数字排序必须配 collation,否则“10”排在“2”前面
字段存的是 "10"、"2" 这类字符串数字时,MongoDB 默认按字节序比较,结果是 "10", "2", "20"。这不是 bug,是设计行为。
- 修复方式:在
aggregate()调用后链式加.collation({locale: "en", numericOrdering: true}) - PyMongo 示例:
collection.aggregate(pipeline).collation({"locale": "en", "numericOrdering": True}) - 关键约束:对应字段的索引也得建在相同
collation下,否则排序不走索引,10 万条就可能触发"Sort exceeded memory limit"
排名依赖索引,没索引的 $sort 就是定时炸弹
即使 pipeline 写得再规范,只要 $sort 字段没建索引,或者索引字段顺序/方向和 $sort 不匹配,MongoDB 就只能内存排序。数据量一过百万,响应延迟飙升,还容易 OOM。
- 索引字段顺序必须和
$sort完全一致,例如{$sort: {category: 1, score: -1}},索引就得是{category: 1, score: -1}或{category: 1, score: 1} - 混合方向建议建双向索引:
db.collection.createIndex({category: 1, score: 1})+db.collection.createIndex({category: 1, score: -1}) - 用
explain("executionStats")检查是否命中索引,重点看executionStats.totalDocsExamined是否接近totalNReturned;若前者远大于后者,说明索引没生效
真正卡住人的从来不是语法,而是排序字段有没有索引、collation 配没配对、唯一性保没保障 —— 这三处任一出问题,排名就从“确定性结果”变成“随机抽签”。











