mongo cursor 游标超时

本文探讨了MongoDB游标超时问题,常见于大数据处理中,提供了增大服务器超时时间、调整batchSize、一次性获取数据和设置noCursorTimeout等解决策略。

mongo cursor 游标超时

mongo cursor 游标超时问题


场景描述

使用 java 程序通过定时任务处理 mongo 中的数据时(mongo 集合中共有近一亿个文档,每次只处理其中的小二十万),出现了以下错误:

org.springframework.data.mongodb.UncategorizedMongoDbException: Query failed with error code 43 and error message 'Cursor not found, cursor id: 412824332348' on server ip:30000; nested exception is com.mongodb.MongoQueryException: Query failed with error code 43 and error message 'Cursor not found, cursor id: 412824332348' on server ip:30000
or
org.springframework.dao.DataAccessResourceFailureException: Query failed with error code -5 and error message 'Cursor 3998850069977484811 not found on server 172.29.181.137:30000' on server ip:30000; nested exception is com.mongodb.MongoCursorNotFoundException: Query failed with error code -5 and error message 'Cursor 3998850069977484811 not found on server 172.29.181.137:30000' on server ip:30000

关于游标(cursor)

当使用 find() 相关函数从 mongo 获取数据时,它返回的并不是数据本身,而是一个游标,且每一个游标都对应一个 id,mongo 服务器会管理这个游标。正真获取数据是用这个游标去 mongo 获取数据;且为了提高 io 利用率,用游标获取数据是批量返回,每一批的大小是由 batch_size 参数决定的,默认是 101 行。正真获取数据的触发时间是在调用 find() 相关函数拿到游标之后,在第一次用 iterator 迭代游标时,客户端会将根据游标拿到的这一批数据放到内存中,然后再用 iterator.next() 一条一条的读取。当内存中的这一批数据迭代完之后,客户端会用这个游标去 mongo 服务器去取下一批数据。

且,游标是 mongo 服务器生成的,是一种系统资源,类似于线程。所以游标用完了需要及时回收。游标有个超时时间,默认为 10min。在超时时间内,如果客户端使用完游标,则会向服务器发送 close 命令,服务器接口到这个命令之后就会回收游标;另一种情况是,在超时时间内,客户端未使用完游标,则服务器会主动回收游标。

// 切换数据库
MongoDatabase mongoDatabase = MongoUtil.getMongoClient("test");

// 获取集合实例
MongoCollection mongoCollection = mongoDatabase.getCollection("collection_name");

// 组装查询条件
BasicDBObject query = new BasicDBObject();
query.put("username", "momo");

// 查询 此时返回游标
FindIterable<Document> findIterable = mongoCollection.find(query);

// 获取迭代器
MongoCursor<Document> mongoCursor = findIterable.iterator();

Document document;
while (mongoCursor.hasNext()) {
    document = mongoCursor.next();
}

游标为什么会找不到?

游标找不到通常有以下两种情况:

  • 1、客户端游标超时,被服务端回收,再用游标向服务器请求数据时就会出现游标找不到的情况。
  • 2、在 mongo 集群环境下,可能会出现游标找不到的情况。
    游标由 mongo 服务器生成,在集群环境下,当使用 find() 相关函数时返回一个游标,假设此时该游标由 A 服务器生成,迭代完数据继续请求数据时,访问到了 B 服务器,但是该游标不是 B 生成的,,此时就会出现游标找不到的情况。
    正常情况下,在 mongo 集群时,会将 mongo 地址以 ip1:port1,ip2:port2,ip3:port3 形式传给 mongo 驱动,然后驱动能够自动完成负载均衡和保持会话转发到同一台服务器,此时不会出现游标找不到的情况。
    但当我们自己搭建了负载均衡层,且用 xxx.xxx.com:port 这种方式来配置时,就会出现游标找不到的情况。

解决方案

针对上面游标找不到的两种情况,一般都是以 ip1:port1,ip2:port2,ip3:port3 这种形式配置的,所以这里只讨论第一种情况。

第一种情况的表象是客户端对游标的使用超过了游标超时时间。

  • 1、在服务端增大 mongo 服务器的游标超时时间。参数是 cursorTimeoutMillis,其默认是 10 min。修改后需重启 mongo 服务器。

    // 启动时设置
    mongod -setParameter cursorTimeoutMillis=60000
    // 在线修改
    db.runCommand({setParameter:1,cursorTimeoutMillis:60000})
    
  • 2、在客户端减少游标每次返回的数据量,使每批数据能在 10 min 内消费完。参数是 batchSize,默认值是 101。
    这种方式的缺点是增加了 mongo 连接次数,比较消耗 io。

    // com.mongodb.client
    FindIterable<Document> findIterable = mongoCollection.find(query).batchSize(50);
    // spring-boot-starter-data-mongodb
    mongoTemplate.find(query.cursorBatchSize(50), Object.class, "collection_name");
    
    // 注:当 batchSize 设置为 -1 时只返回一条数据
    
  • 3、在客户端一次性获取到全部符合条件的数据。可以通过两种方式设置,分别是 batchSize 和 exhaust。
    这种方式的缺点是,由于一次性返回了所有数据,对系统内存要求较高。

    // batchSize
    batchSize(Integer.MAX);
    cursorBatchSize(Integer.MAX);
    
    // 如果数据量过大或处理过程过慢依旧会出现游标超时的情况 可以配合 noCursorTimeout 来解决 即
    batchSize(Integer.MAX).noCursorTimeout();
    cursorBatchSize(Integer.MAX).noCursorTimeout();
    
    // exhuast 作用是在一个游标批次中返回全量数据
    mongoTemplate.find(query.exhuast(), Object.class, "collection_name");
    
    // 使用 exhaust 时需注意以下几点
    // 1、mongo cluster 不支持此设置
    // 2、EXHAUST 与 limit() 不兼容
    // 3、使用此模式时需忽略网络超时情况
    
  • 4、客户端设置游标永不超时。参数 noCursorTimeout 为 true。
    这种方式的缺点是,如果程序意外停止或异常,该游标永远不会被释放,除非重启 mongo,否则会一直占用系统资源,属于危险操作。

    // com.mongodb.client
    FindIterable<Document> findIterable = mongoCollection.find(query).noCursorTimeout(true);
    // spring-boot-starter-data-mongodb
    mongoTemplate.find(query.noCursorTimeout(), Object.class, "collection_name");
    

@XGLLHZ-XXX.mp3

<think>好的,我现在需要向用户介绍“循环查询Mongo”。首先,我得理解用户的具体需求是什么。他们可能是指在使用MongoDB数据库时,如何通过循环来执行查询操作,或者可能是在处理大量数据时如何进行分页查询,即循环获取数据。也有可能是其他与循环相关的查询场景,比如遍历集合中的文档进行处理。 接下来,我应该明确MongoDB的基本查询机制。MongoDB本身不支持传统的SQL中的游标循环,但可以通过游标cursor)来遍历查询结果。此外,分页查询也是常见需求,特别是处理大量数据时,避免一次性加载全部数据导致内存问题。还有批量操作,比如批量更新或删除,可能需要循环处理符合条件的文档。 然后,我需要考虑用户可能的编程语言环境。虽然问题没有明确说明,但常见的如Node.js、Python等与MongoDB交互的语言。不同语言的驱动可能有不同的实现方式,但核心概念类似。比如在Node.js中使用`forEach`方法遍历游标,或者在Python中使用循环结构处理游标对象。 分页查询的实现需要解释使用`skip`和`limit`方法,以及可能的性能优化,比如基于条件的分页而不是简单的skip/limit,尤其是在大数据量下skip效率较低的问题。这里可能需要提到使用范围查询(比如基于时间戳或ID)来提高分页效率。 另外,批量操作方面,MongoDB提供了bulk write操作,但某些情况下可能需要循环处理,比如逐条更新文档中的某些字段。这时候需要在循环中执行更新操作,并注意性能问题,比如批量提交而非逐条提交。 还需要提醒用户注意性能问题,尤其是在循环中频繁执行数据库操作可能导致性能瓶颈。建议合理使用批量操作、索引优化,以及避免不必要的查询。例如,在循环内部尽量减少数据库交互次数,或者使用聚合框架来处理复杂逻辑,减少客户端处理的数据量。 可能用户会遇到的问题包括游标超时,特别是在长时间运行的循环中,MongoDB游标默认会在一定时间后关闭。这时候需要设置noCursorTimeout选项,但也要注意及时关闭游标释放资源。此外,错误处理也很重要,比如在循环中捕获异常,避免整个程序因个别文档处理失败而中断。 最后,总结循环查询MongoDB的主要场景和实现方式,强调根据具体需求选择合适的方法,并注意性能和资源管理。同时,提供示例代码框架帮助用户理解,比如使用分页查询的代码结构,或使用游标遍历的示例。</think>### 循环查询 MongoDB 的常见场景与实现方法 循环查询 MongoDB 通常用于以下场景: 1. **分页遍历大量数据**(避免内存溢出) 2. **批量处理符合条件的文档** 3. **逐条处理查询结果** 4. **轮询监控数据变化** --- #### 一、基础实现:游标遍历 MongoDB 查询返回的是游标cursor),可通过循环逐条处理: ```javascript // 伪代码示例(Node.js驱动) const cursor = db.collection('users').find({ age: { $gt: 18 } }); while (await cursor.hasNext()) { const doc = await cursor.next(); console.log(doc.name); // 处理每条数据 } ``` **优点**:自动管理内存,适合大数据量 **缺点**:游标默认10分钟超时(需设置 `noCursorTimeout`) --- #### 二、分页查询实现 通过 `skip` + `limit` 分段读取数据: ```javascript let page = 0; const pageSize = 100; while (true) { const docs = await db.collection('orders') .find({ status: "pending" }) .skip(page * pageSize) .limit(pageSize) .toArray(); if (docs.length === 0) break; // 处理当前页数据 processDocs(docs); page++; } ``` **优化建议**: 1. 避免过大页码(`skip` 性能差) 2. 改用范围查询(如基于 `_id` 或时间戳) --- #### 三、批量操作循环 对需要修改的文档进行批量处理: ```python # Python 伪代码示例 from pymongo import UpdateOne batch = [] for doc in collection.find({"flag": None}): batch.append( UpdateOne({"_id": doc["_id"]}, {"$set": {"flag": 1}}) ) if len(batch) == 500: collection.bulk_write(batch) batch = [] if batch: # 处理剩余数据 collection.bulk_write(batch) ``` **优势**:减少网络往返次数,提升性能 --- #### 四、定时轮询查询 通过循环实现数据监控: ```javascript setInterval(async () => { const newLogs = await db.collection('logs') .find({ timestamp: { $gt: lastCheckTime } }) .toArray(); if (newLogs.length > 0) { alertNewLogs(newLogs); lastCheckTime = newLogs[newLogs.length-1].timestamp; } }, 5000); // 每5秒检查一次 ``` --- ### 关键注意事项 1. **索引优化**:确保查询字段有索引(特别是分页字段) 2. **游标管理**:及时关闭游标(`cursor.close()`) 3. **批量大小**:推荐每批处理100-1000条文档 4. **错误处理**:添加 `try-catch` 应对网络波动 5. **性能监控**:观察 `explain()` 结果优化查询计划 --- ### 高级场景 1. **变更流(Change Stream)**:实时监听数据变化(替代轮询) ```javascript const changeStream = collection.watch(); for await (const change of changeStream) { handleChange(change); } ``` 2. **聚合管道循环**:使用 `$lookup` 实现类似联表查询 根据具体需求选择合适的循环查询策略,可显著提升处理效率和系统稳定性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

红衣女妖仙

行行好,给点吃的吧!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值