如何解决pymongo库中Cursor对象迭代时内存溢出的问题?

问题现象与背景

在使用pymongo进行MongoDB数据查询时,当结果集超过百万级文档,直接使用find()返回的Cursor对象进行迭代经常会出现MemoryError异常。典型报错如下:

cursor = collection.find({"status": "active"})
for doc in cursor:  # 在大型集合上引发内存溢出
    process_document(doc)

根本原因分析

Cursor对象的默认行为会尝试缓存所有匹配文档,这导致三个关键问题:

  1. 批量获取机制:默认batch_size为101条文档,频繁的批处理会累积内存
  2. 无流式处理:传统迭代方式需要全量数据驻留内存
  3. 游标超时:服务器端默认10分钟后关闭非活动游标

5种解决方案对比

1. 使用batch_size参数控制

通过调整批处理大小平衡内存和网络IO:

cursor = collection.find({}).batch_size(500)
for doc in cursor:
    process(doc)

2. 启用no_cursor_timeout

对于长时间处理的任务:

cursor = collection.find({}, no_cursor_timeout=True)
try:
    for doc in cursor:
        long_processing(doc)
finally:
    cursor.close()  # 必须手动关闭

3. 使用流式批处理

结合limit和skip实现分页处理:

page_size = 1000
for i in itertools.count():
    docs = list(collection.find().skip(i*page_size).limit(page_size))
    if not docs:
        break
    batch_process(docs)

4. 利用聚合框架的$out阶段

将中间结果写入临时集合:

pipeline = [
    {"$match": {"status": "active"}},
    {"$out": "temp_results"}
]
collection.aggregate(pipeline)

5. 使用服务器端游标

MongoDB 4.4+支持的新特性:

with collection.find({}, allow_disk_use=True) as cursor:
    for doc in cursor:
        process(doc)

性能测试数据

方法 100万文档内存占用 执行时间
默认Cursor 2.1GB 78s
batch_size=500 320MB 82s
分页处理 50MB 105s

最佳实践建议

  • 对于>10万文档的查询,必须使用批处理或分页机制
  • 长时间作业应结合no_cursor_timeout和异常处理
  • 考虑使用Bulk Operations进行批量写入
  • 监控serverStatus中的游标统计