$rank 和 $denseRank 不能直接当行号用,因为它们是并列排名函数:$rank 对重复值跳过后续序号(如1,1,4),$denseRank 不跳过但也不保证全局连续(如1,1,2),均不等价于从1开始逐行递增的物理行序;真正行号需用 $cumsum 配合 $const: 1,并确保上游已正确 $sort。

为什么 $rank 和 $denseRank 在 MongoDB 5.0 里不能直接当行号用
因为 $rank 和 $denseRank 是排名函数,不是行号生成器——它们对相同值会给出相同排名,且跳过后续序号($rank)或不跳过($denseRank),但都不保证从 1 开始连续递增的“物理行序”。真正需要行号(row number),得靠 $cumsum 配合 $const: 1,或者用 $indexOfArray + $range(仅限内存可容纳数组时)。
用 $cumsum 实现稳定、可控的行号
这是 MongoDB 5.0+ 最推荐的行号方案,不依赖排序字段是否重复,也不受分组影响,只要 pipeline 顺序确定,结果就确定。
-
$set阶段中使用{ rowNumber: { $cumsum: { input: { $const: 1 } } } } - 必须确保上游已按目标顺序
$sort,否则行号顺序错乱 - 不能在
$group内直接用$cumsum生成全局行号(它只在当前文档流中累加);如需每组内编号,需先$group再$unwind+$sort+$cumsum - 示例片段:
{ $set: { rowNumber: { $cumsum: { input: { $const: 1 } } } } }
$rank 和 $denseRank 的适用场景和坑
它们只应在明确需要“并列排名”的业务逻辑中使用,比如排行榜、成绩分档。误当行号会导致数据错位。
-
$rank对 score=95 的三人返回 1,1,1,下一位是 4(跳过 2 和 3);$denseRank则返回 1,1,1,2 - 必须配合
$setWindowFields使用,且partitionBy和sortBy缺一不可;漏写sortBy会报错"sortBy is required for window functions" - 不能在
$project中直接调用$rank;必须走$setWindowFields管道阶段 - 示例结构:
{ $setWindowFields: { sortBy: { createdAt: 1 }, output: { rank: { $rank: {} } } } }
性能与限制:$cumsum 比 $rank 更轻量,但要注意内存
$cumsum 是逐文档计算,无排序依赖开销;而 $rank/$denseRank 在窗口内强制排序,大数据集可能触发 "Sort exceeded memory limit" 错误。
- 对千万级集合,
$setWindowFields很可能失败,除非加allowDiskUse: true -
$cumsum没有内存限制警告,但若上游未索引$sort,排序本身仍慢 - 聚合管道中,
$cumsum只能出现在$set或$addFields,不能用于$match条件(因是运行时计算值)
$cumsum,后者才考虑 $rank。很多人卡在第一步没想清这个区别。


















