Cursor超时问题的本质
当使用pymongo.collection.find()方法查询大量数据时,返回的Cursor对象默认会在10分钟后因服务器端超时(cursor timeout)而自动关闭。这是MongoDB的自我保护机制,防止长期占用系统资源的查询存在。
5种解决方案对比
1. 设置no_cursor_timeout参数
cursor = db.collection.find(
{"status": "active"},
no_cursor_timeout=True
)
优点:完全禁用超时机制
风险:可能导致内存泄漏,必须显式调用close()
2. 使用batch_size优化
cursor = db.collection.find().batch_size(500)
通过减小批处理大小降低单次操作耗时,使每次getMore命令能在超时前完成。
3. 分片查询策略
将大查询分解为基于_id范围的多个子查询:
last_id = None
while True:
query = {"_id": {"$gt": last_id}} if last_id else {}
cursor = db.collection.find(query).limit(1000)
if not cursor.alive: break
for doc in cursor:
last_id = doc["_id"]
4. 使用snapshot模式
cursor = db.collection.find().snapshot(True)
防止文档在遍历期间被移动,但会降低写入性能。
5. 定期重置游标
通过定时发送killCursors和重新查询来刷新超时计时器:
import threading
def reset_cursor(cursor, interval=300):
while True:
time.sleep(interval)
cursor.close()
new_cursor = db.collection.find(cursor._filter)
cursor.__dict__.update(new_cursor.__dict__)
性能优化建议
- 配合projection减少返回字段
- 合理使用index覆盖查询
- 监控oplog增长率
- 考虑aggregation pipeline替代复杂查询
异常处理最佳实践
必须捕获CursorNotFound异常并实现重试逻辑:
from pymongo.errors import CursorNotFound
try:
for doc in cursor:
process(doc)
except CursorNotFound:
logger.warning("Cursor expired, restarting...")
restart_query()