使用MongoDB C Driver游标更新文档后,二次读取失败求助
问题:MongoDB C Driver更新操作后游标next失败,无错误信息
我正在使用最新版本的MongoDB C Driver(2.3.1)和MongoDB 8.3,发现以下代码在首次更新后,第二次调用cursor next时失败,且错误变量为空,无法排查原因。
若将更新逻辑改为删除文档,该代码可正常运行,这一现象十分奇怪。
请问为何更新操作会导致cursor next失败?
// This is an example of our code. // There are several thousand rows in the collection. // // Do update. // Note: We are in a transaction. boolean doUpdate( mongoc_collection_t* m_collection) { boolean success = true; bson_t * m_readDoc = bson_new(); bson_t * m_updateDoc = bson_new(); bson_t* m_query = bson_new(); bson_iter_t it; bson_iter_t it_id; bson_value_t* m_it_val; bson_error_t m_error; // Get a cursor for the collection. mongoc_cursor_t* m_cursor = mongoc_collection_find_with_opt(m_collection, m_query, NULL, NULL); BSON_DESTROY(m_query); // 1) First next works. // 2) We make a call to alter data. // 3) The update is succesfull. // 4) The next read fails. while (mongoc_cursor_next(m_cursor, &m_readDoc)) { // Get the document ID m_query = bson_new(); if (bson_iter_init(&it, m_readDoc) && bson_iter_find_descendant(&it, "_id", &it_id)) { m_it_val = bson_iter_value(&it_id); BSON_APPEND_OID(m_query, "_id", &m_it_val->value.v_oid); } else { BSON_DESTROY(m_query); success = false; break; } // Make changes to the document. m_updateDoc = makeChanges(m_readDoc); if (!mongoc_collection_replace_one(m_collection, m_query, m_updateDoc, NULL, NULL, &m_error)) { BSON_DESTROY(m_updateDoc); BSON_DESTROY(m_query); printf("Update error %s\n", m_error.message); success = false; break; } else { BSON_DESTROY(m_updateDoc); BSON_DESTROY(m_query); } } // When the second next fails, no error is return. if (mongoc_cursor_error(m_cursor, &m_error)) { printf("Error on read %s\n", m_error.message); success = false; } mongoc_cursor_destroy(m_cursor); return success; }
原因分析
- 事务内非快照游标的特性:你当前使用的是默认游标,在事务中这类游标不会创建数据集快照。当更新操作修改了文档在索引中的位置(比如修改了索引键),游标会丢失后续文档的定位,导致
next调用失败。而删除操作只会让游标跳过已删除文档,不会破坏遍历的连续性。 - 错误未暴露的原因:事务内的游标失效属于MongoDB内部的“正常终止”场景,驱动未将其标记为显式错误,因此
mongoc_cursor_error无法捕获到相关信息。
解决方案
- 启用快照游标:创建游标时设置快照选项,让游标基于查询时刻的数据集快照遍历,不受后续更新操作影响。修改游标创建代码如下:
mongoc_find_opt_t *find_opt = mongoc_find_opt_new(); // 启用快照模式 mongoc_find_opt_set_snapshot(find_opt, true); mongoc_cursor_t* m_cursor = mongoc_collection_find_with_opt(m_collection, m_query, find_opt, NULL); // 释放选项对象 mongoc_find_opt_destroy(find_opt);
- 先批量获取ID再更新:先遍历游标将所有需要更新的文档ID存入内存列表,关闭游标后再逐个执行更新操作,彻底避免游标遍历与修改操作的冲突。
- 避免修改索引相关字段:如果不需要快照,确保更新的字段不包含游标查询依赖的索引键或排序字段,这样文档在索引中的位置不会改变,游标可以正常继续遍历。
内容的提问来源于stack exchange,提问作者Phil C
相关产品推荐
相关产品推荐

