终止MongoDB聚合游标,并不是在聚合管道里追加一个阶段,而是通过数据库命令或客户端关闭操作完成。MongoDB官方文档的聚合阶段列表中并没有$killCursors,实际可用的是killCursors数据库命令以及各语言驱动的cursor.close方法。把这两件事区分清楚,才能避免在排查游标泄漏时走弯路。

聚合游标的执行模型与资源占用
聚合管道在mongod或mongos上执行,客户端发起aggregate命令后,服务端会生成一个游标对象并返回第一批文档。后续批次的读取通过getMore命令完成,游标ID是每个游标的唯一标识。只要客户端没有关闭游标,服务端就会在会话缓存中维护这个游标的状态。
游标占用资源的大小取决于管道阶段。例如$sort和$group可能需要将大量文档保存在内存中;当工作集超过100MB时,如果没有设置allowDiskUse,查询会直接失败,而设置后则会写入磁盘临时文件。即使查询已经返回了第一批结果,只要后续批次未被消费,这些临时文件和相关内存就不会被释放。
默认情况下MongoDB服务端会给空闲游标设置一个超时时间,超过后自动清理,但业务高峰期这10分钟可能已经造成明显压力。因此在批处理、导出任务或客户端异常退出时,显式终止游标比依赖自动超时更可靠。
killCursors命令与$killCursors误区
严格来说,聚合管道阶段中不存在$killCursors。MongoDB的聚合表达式和阶段只能做数据转换、判断和输出,不能直接向服务端发送管理命令,也不能修改其他会话的状态。所谓$killCursors,更多是killCursors命令在部分文档或博客中被错误加上了美元符号。
如果误把$killCursors写进pipeline数组,MongoDB会返回Unrecognized pipeline stage name错误。这是因为聚合管道只接受数据转换阶段,管理类命令必须通过runCommand接口发送。下面就是正确的killCursors命令语法。
db.runCommand({
killCursors: "orders",
cursors: [NumberLong("7789012345678901234")]
})
这里要注意游标ID是64位整数,超出JavaScript普通数值精度时必须使用NumberLong包装,否则可能因为精度丢失而无法匹配。实际使用中更推荐先通过聚合命令的返回结果拿到cursor.id,或者使用currentOp命令查看空闲游标。
let result = db.runCommand({
aggregate: "orders",
pipeline: [ { $match: { status: "pending" } } ],
cursor: { batchSize: 5 }
})
let cursorId = result.cursor.id
print(cursorId)
如果已经从聚合结果里获得游标ID,直接把它传入killCursors即可完成终止。这个命令可在mongosh、Node.js、Java等任何驱动中通过runCommand发送。
客户端如何正确终止聚合游标
在mongosh中可以为一个聚合查询赋值给变量,然后调用close方法。close方法内部会向服务端发送killCursors命令,比放任对象等待垃圾回收更及时。
const cursor = db.orders.aggregate([
{ $match: { region: "华东" } },
{ $group: { _id: "$city", total: { $sum: "$amount" } } }
])
cursor.close()
在实际项目中,不一定总是用shell操作。Java驱动可以使用Cursor接口的close方法,Node.js驱动同样有cursor.close,Python驱动则通过cursor.close实现。无论哪种语言,都建议把关闭游标放在finally块中,防止消费过程中抛出异常导致游标悬挂。
const cursor = collection.aggregate([
{ $match: { createdAt: { $gte: start } } }
])
try {
while (await cursor.hasNext()) {
const doc = await cursor.next()
// 处理文档
}
} finally {
await cursor.close()
}
上面的代码在Node.js环境中,即使处理文档时抛错,finally也会确保游标关闭。对于只需要前N条结果就可以结束的场景,不要只break出循环而不关闭游标,因为break只终止了客户端迭代,服务端游标仍然存在。
监控游标泄漏与自动化清理
当线上出现实例内存缓慢增长、临时磁盘占用升高时,可以先用currentOp命令列出所有活跃和空闲的游标。通过idleCursors参数打开空闲游标返回能力,然后过滤type为idleCursor的结果。
db.adminCommand({
currentOp: 1,
idleCursors: true,
maxTimeMS: 5000
})
命令返回结果中除了正在执行的操作,还会包含空闲游标信息,每个条目里有cursorId、ns、lsid等字段。根据ns找到目标集合,再用killCursors批量清理。自动化脚本可以定期查询超过阈值的空闲游标,生成清理命令。
需要注意的是,不要直接终止正在被活跃消费的游标,否则该客户端下一次getMore会收到Cursor NotFound错误。清理脚本最好只处理空闲时间较长的游标,并通过白名单排除核心业务会话。
MongoDB聚合管道killCursors游标终止修改时间:2026-08-28 00:26:27