Motor游标默认支持async for遍历,但需避免提前await游标、调用to_list()等导致游标耗尽;应直接赋值cursor = collection.find(...)后async for遍历,并注意超时、索引、生命周期等服务端约束。
async for直接遍历?Motor 3.0+ 版本中,find() 返回的 AsyncIOMotorCursor 确实支持 async for,但前提是:你没调用过 to_list()、next() 或提前 await 了游标本身。常见错误是误以为“返回游标就得先 await”,结果写成 cursor = await collection.find(...) —— 这会立刻触发查询并把整个游标对象变成一个不可再迭代的“已消耗”状态,后续 async for doc in cursor 就会报 RuntimeError: async generator already exhausted。
cursor = collection.find(...)(不 await),再 async for doc in cursor
.limit(n),别用 to_list(n),后者会一次性拉取全部到内存MongoDB 默认游标超时时间为 10 分钟(maxTimeMS 不影响这个),一旦空闲超过阈值,服务端会销毁游标,再次 async for 就会抛 ExecutionTimeout 或 InvalidOperation: cursor does not exist。这不是 Motor 的 bug,而是 MongoDB 的设计。
collection.find(..., no_cursor_timeout=True)
max_await_time_ms(仅适用于 tailable cursor)或业务层心跳:每处理 N 条就 await asyncio.sleep(0) 让出控制权,避免 event loop 长时间阻塞async for 循环里做同步阻塞操作(如 time.sleep、文件读写),否则游标空闲时间会被拉长async for 遍历时能否中途修改查询条件或跳过数据?不能。MongoDB 游标是前向只读的,Motor 的 AsyncIOMotorCursor 不提供 skip()、rewind() 或动态改 filter 的能力。所谓“跳过前 N 条”,必须在执行 find() 前通过 .skip(N) 设置;“按条件中断”只能靠 Python 层 break,不会通知 MongoDB 提前终止游标。
_id 范围查询替代 skip():记录上一批最后的 doc['_id'],下一次查 {'_id': {'$gt': last_id}}
sort 和 filter 中,而不是依赖循环内判断async for 每次迭代实际发起一次 getMore 请求(batch size 默认 101),不是真的一条一条发网络包async for 看起来“卡住”没输出?最常见原因是:查询没命中索引,MongoDB 执行全表扫描,而 Motor 在等待第一批 batch 返回。此时 async for 会挂起,直到服务端返回首个结果块,期间没有任何日志或 timeout 提示。
立即学习“Python免费学习笔记(深入)”;
explain() 检查执行计划:await collection.find({...}).explain(),确认 executionStats.nReturned > 0 且 stage 是 IXSCAN 而非 COLLSCAN
sort() 字段有对应索引,否则游标无法高效推进(尤其带 limit() 时)print(f"got {doc.get('_id')}"),但生产环境避免高频 print,它可能成为性能瓶颈