You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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无法捕获到相关信息。

解决方案

  1. 启用快照游标:创建游标时设置快照选项,让游标基于查询时刻的数据集快照遍历,不受后续更新操作影响。修改游标创建代码如下:
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);
  1. 先批量获取ID再更新:先遍历游标将所有需要更新的文档ID存入内存列表,关闭游标后再逐个执行更新操作,彻底避免游标遍历与修改操作的冲突。
  2. 避免修改索引相关字段:如果不需要快照,确保更新的字段不包含游标查询依赖的索引键或排序字段,这样文档在索引中的位置不会改变,游标可以正常继续遍历。

内容的提问来源于stack exchange,提问作者Phil C

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 14:17:09