采用 pymongo 读取 MongoDB 数据时,一般写法:

import pymongo

coll = pymongo.MongoClient().db.col

for doc in coll.find():
    print(doc)

其中,在 for doc in coll.find() 行可能会报错.

coll.find() 并不是直接返回数据库数据,而是一个 cursor(游标)对象. 当使用 for 循环开始迭代该对象的时候,cursor 才会真正去读取数据库数据.

pymongo 会一次性读取 100 行数据,for doc in coll.find() 循环第一次的时候,会自动连接 mongodb,拿到 100 条数据,在内存中进行缓存. 因此,在 2-100 次 for 循环时,数据是直接从内存里读取的,并不会再重复连接数据库.

但,当循环到 101 次时,pymongo 会再一次连接数据库,再拿到 100 条数据,依次循环.

基于这种方式,pymongo 能够有效的降低网络 I/O 耗时.

然而,MongoDB 默认 cursor 的超时时间是 10 分钟,过了超时时间,必须再次连接 mongodb 刷新 cursor 时间,否则就会出现 cursor 超时问题.

对此,一般有如下几种方案:

1. 修改 MongoDB 配置

修改 MongoDB 配置,延长 cursor 超时时间,并重启 MongoDB.

但,对于MongoDB 不能轻易重启的场景,如生产环境,需要慎重.

2. 一次性数据加载到内存

仅限于数据量不大,或者内存足够大时.

此外,还会面临再次 for 遍历.

3. 减少 cursor batch_size

cursor 默认每次返回 100 条数据. 可以减少 batch_size,使得一批数据的处理时间小于 10 分钟.

如,

for doc in coll.find().batch_size(50):
    print(doc)

但,会增加数据库连接频次,增加 I/O 时间.

4. 设定 no_cursor_timeout=True

设定 no_cursor_timeout=True 保持 cursor 不超时.

如:

cursor = coll.find(no_cursor_timeout=True)
for doc in cursor:
    print(doc)
cursor.close()

但,这种操作方式比较危险,因为如果 pymongo 程序一旦意外停止,cursor 无法停止,直到 MongoDB 重启,将一直占用资源.

因此,可以采用 with 上下文管理器,

with coll.find(no_cursor_timeout=True) as cursor:
    for doc in cursor:
        print(doc)
Last modification:May 30th, 2022 at 11:17 am