motor的aggregate()返回asynciomotorcommandcursor,是支持async for和to_list()的异步游标,非同步生成器;不可用普通for或list(),await cursor本身非法;to_list(none)易致oom,async for才是流式处理正解。

Motor 的聚合查询返回 AsyncIOMotorCommandCursor,它不是 Generator,而是**可迭代的异步游标对象**——但它的迭代方式必须用 async for 或 to_list(),不能用普通 for。很多人误以为它是“Generator”,其实是混淆了同步生成器(generator)和异步迭代器(AsyncIterator)的行为。
为什么 aggregate() 不直接返回 list?
MongoDB 聚合结果可能极大,Motor 默认不加载全部数据到内存,而是返回一个惰性游标。这个游标实现了 __aiter__,所以支持 async for;但它**不是 Python 的 generator 函数返回的那种 generator 对象**,也没有 send()、throw() 等方法。
- 直接写
for doc in collection.aggregate([...])会报错:TypeError: 'AsyncIOMotorCommandCursor' object is not iterable - 写
list(collection.aggregate([...]))同样失败:它不支持同步转 list - 正确路径只有一条:要么
await cursor.to_list(length=None),要么async for doc in cursor:
await cursor.to_list() 是最常用但最危险的操作
to_list() 看起来简单,但极易引发 OOM 或响应延迟:
-
await cursor.to_list(length=1000):最多取 1000 条,适合已知小结果集 -
await cursor.to_list(length=None):等价于全量加载,大数据量时内存暴涨,服务可能被拖垮 - 即使加了
limit阶段,MongoDB 仍可能在内存中构建中间结果(如$group),to_list()只是最后一步“拉取”,不缓解聚合压力 - 如果管道里有
$lookup且被关联集合没索引,慢查询会卡住整个 asyncio event loop,用户请求超时而非报错
async for 才是流式处理的正解
真正应对大聚合结果的方式是放弃“一次性拿完”,改用异步迭代逐条消费:
- 必须用
async for doc in cursor:,不能漏掉async - 每轮迭代都是 awaitable,事件循环可在此处切走处理其他协程
- 内存占用恒定,适合日志分析、导出 CSV、ETL 中间转发等场景
- 注意:若在
async for内部做耗时同步操作(比如json.dumps(doc)大文档),仍会阻塞 event loop——应拆成await asyncio.to_thread(json.dumps, doc)(Python 3.9+)
别把 cursor 当协程对象,await 它没意义
collection.aggregate([...]) 本身是同步调用,返回的是游标对象,不是协程。你不能写 await collection.aggregate([...]),这会报错:TypeError: object AsyncIOMotorCommandCursor can't be used in 'await' expression。
- 错误写法:
cursor = await collection.aggregate([...]) - 正确写法:
cursor = collection.aggregate([...]),然后对cursor做to_list()或async for - 唯一要
await的是游标上的方法:await cursor.to_list(...)、await cursor.next()、await cursor.close()
to_list(length=None) 就可能让线上服务在流量高峰时集体卡死——它不报错,只是变慢、OOM、连接池耗尽。而 async for 的正确使用,往往需要配合业务逻辑做分批缓冲或背压控制,不是加个 async 就万事大吉。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











