聚合管道不支持全局自增序列,因其无状态、不持久、不可跨请求保证唯一性;真正自增ID必须通过外部原子写操作(如findOneAndUpdate)实现,聚合仅适合读取时附加临时序号。
聚合管道本身不支持“全局自增序列”——它没有状态、不跨文档持久计数,$addFields + $sum 或 $range 只能生成**当前批次内的相对序号**,无法替代计数器集合那种跨请求、可持久、带原子性的自增 ID。
为什么不能在 $project 里用 $sum 或 $indexOfArray 模拟自增
常见误解是想靠 $sum 累加一个常量,或用 $indexOfArray 查当前文档位置来“编号”。但问题在于:
- 聚合是无状态的:每次运行都从头开始,不记住上次用了几
- 结果顺序依赖
$sort,而$sort本身不保证稳定(尤其含相同字段值时) -
$indexOfArray需先用$group把所有文档收进一个数组,内存和性能随数据量陡增,10 万文档就可能 OOM - 并发执行多个聚合时,完全无法协调序号唯一性
真正需要自增 ID 时,必须跳出聚合管道
聚合管道只适合“查”,不适合“改+记”。要生成业务上可用的自增字段(如订单号、用户 ID),必须配合外部原子写操作:
MongoDB For Windows v3.5.4
MongoDB For Windows v3.5.4
下载
- 先用
db.counters.findOneAndUpdate安全取号,拿到seq值 - 再把这个值作为字段注入到插入/更新语句中,比如
db.orders.insertOne({ _id: nextId, ... }) - 如果硬要在聚合结果里“附带”序号(仅用于前端分页展示、导出排序等非主键用途),可用
$setWindowFields(4.4+):
db.orders.aggregate([
{ $sort: { createdAt: 1 } },
{
$setWindowFields: {
sortBy: { createdAt: 1 },
output: { rowNumber: { $documentNumber: {} } }
}
}
])
注意:$documentNumber 是窗口内序号,不是数据库级自增;它不写入文档,也不保证后续聚合结果一致。
容易被忽略的关键点
很多人试过 $inc 直接写聚合里的 $addFields,但 $inc 是更新操作符,**不能出现在聚合表达式中**——它只在 update、findOneAndUpdate 等写操作里合法。试图这么写会报错:Unrecognized expression '$inc'。真正的自增永远发生在写阶段,不在读(聚合)阶段。