Mongodb源码分析--更新记录

 在之前的一篇文章 中,介绍了assembleResponse函数(位于instance.cpp第224行),它会根据op操作枚举类型来调用相应的crud操作,枚举类型定义如下:
  
  1. enum  Operations {  
  2.     opReply  =   1 ,      /*  reply. responseTo is set.  */  
  3.     dbMsg  =   1000 ,     /*  generic msg command followed by a string  */  
  4.     dbUpdate  =   2001 ,  /*  更新对象  */  
  5.     dbInsert  =   2002 ,  
  6.      // dbGetByOID = 2003,  
  7.     dbQuery  =   2004 ,  
  8.     dbGetMore  =   2005 ,  
  9.     dbDelete  =   2006 ,  
  10.     dbKillCursors  =   2007  
  11. };  
 

    可以看到dbUpdate = 2001 为更新操作枚举值,下面我们看一下assembleResponse在确定是更新操作时调用的方法,如下:  
   
  1. // instance.cpp文件第224行  
  2.     assembleResponse( Message  & m, DbResponse  & dbresponse,  const  SockAddr  & client ) {  
  3.     .....  
  4.              try  {  
  5.                  if  ( op  ==  dbInsert ) {   // 添加记录操作  
  6.                     receivedInsert(m, currentOp);  
  7.                 }  
  8.                  else   if  ( op  ==  dbUpdate ) {  // 更新记录  
  9.                     receivedUpdate(m, currentOp);  
  10.                 }  
  11.                  else   if  ( op  ==  dbDelete ) {  // 删除记录  
  12.                     receivedDelete(m, currentOp);  
  13.                 }  
  14.                  else   if  ( op  ==  dbKillCursors ) {  // 删除Cursors(游标)对象  
  15.                     currentOp.ensureStarted();  
  16.                     logThreshold  =   10 ;  
  17.                     ss  <<   " killcursors  " ;  
  18.                     receivedKillCursors(m);  
  19.                 }  
  20.                  else  {  
  21.                     mongo::log()  <<   "     operation isn't supported:  "   <<  op  <<  endl;  
  22.                     currentOp.done();  
  23.                     log  =   true ;  
  24.                 }  
  25.             }  
  26.           .....  
  27.         }  
  28.     }  
 

    从上面代码可以看出,系统在确定dbUpdate操作时,调用了receivedUpdate()方法(位于instance.cpp文件第570行),下面是该方法的定义:


 

  1. void  receivedUpdate(Message &  m, CurOp &  op) {         
  2.         DbMessage d(m); // 初始化数据库格式的消息  
  3.          const   char   * ns  =  d.getns(); // 获取名空间,用于接下来insert数据  
  4.         assert( * ns);  
  5.          // 因为CUD操作在主库中操作,所以这里断言名空间包含的db信息中是不是主库,即"master"  
  6.         uassert(  10054  ,   " not master " , isMasterNs( ns ) );  
  7.         op.debug().str  <<  ns  <<   '   ' ;  
  8.          // 获取标志位信息(标识更新一条或多条等)关于消息结构体。有关消息结构参见我的这篇文章:  
  9.          // http://www.cnblogs.com/daizhj/archive/2011/04/02/2003335.html  
  10.          int  flags  =  d.pullInt();          
  11.          // 获取"更新消息"结构体中的selector(也就是要更新的数据条件,相关于where)  
  12.         BSONObj query  =  d.nextJsObj();  
  13.         assert( d.moreJSObjs() );  
  14.         assert( query.objsize()  <  m.header() -> dataLen() );  
  15.         BSONObj toupdate  =  d.nextJsObj(); // 要更新的记录  
  16.         uassert(  10055  ,  " update object too large " , toupdate.objsize()  <=  BSONObjMaxUserSize);  
  17.         assert( toupdate.objsize()  <  m.header() -> dataLen() );  
  18.         assert( query.objsize()  +  toupdate.objsize()  <  m.header() -> dataLen() );  
  19.          // 标识是否为upsert方式,即:如果存在就更新,如果不存在就插入  
  20.          bool  upsert  =  flags  &  UpdateOption_Upsert;  
  21.          // 是否更新所有满足条件(where)的记录  
  22.          bool  multi  =  flags  &  UpdateOption_Multi;  
  23.          // 是否更新所有节点(sharding状态)  
  24.          bool  broadcast  =  flags  &  UpdateOption_Broadcast;  
  25.         {  
  26.              string  s  =  query.toString();  
  27.              /*  todo: we shouldn't do all this ss stuff when we don't need it, it will slow us down. 
  28.                instead, let's just story the query BSON in the debug object, and it can toString() 
  29.                lazily 
  30.              */  
  31.             op.debug().str  <<   "  query:  "   <<  s;  
  32.             op.setQuery(query);  
  33.         }  
  34.         writelock lk;  
  35.          //  如果不更新所有节点(sharding)且当前物理结点是shard 状态时  
  36.          if  (  !  broadcast  &&  handlePossibleShardedMessage( m ,  0  ) )  
  37.              return ;  
  38.          // if this ever moves to outside of lock, need to adjust check Client::Context::_finishInit  
  39.         Client::Context ctx( ns );  
  40.         UpdateResult res  =  updateObjects(ns, toupdate, query, upsert, multi,  true , op.debug() ); // 更新对象  
  41.         lastError.getSafe() -> recordUpdate( res.existing , res.num , res.upserted );  //  for getlasterror  
  42.     }  

 

     上面的方法中,主要是对消息进行折包解析,找出要更新的数据记录及相应查询条件,以及更新方式(即upsert),然后再在“写锁”环境下执行更新数据操作。
   
     最终上面代码会调用 updateObjects()方法,该方法定义如下:

 
  1. // update.cpp 文件第1279行  
  2.  UpdateResult updateObjects( const   char   * ns,  const  BSONObj &  updateobj, BSONObj patternOrig,  bool  upsert,  bool  multi,  bool  logop , OpDebug &  debug ) {  
  3.        // 断言记录的ns是否在"保留的$集合"中  
  4.       uassert(  10155  ,  " cannot update reserved $ collection " , strchr(ns,  ' $ ' )  ==   0  );  
  5.        if  ( strstr(ns,  " .system. " ) ) {  
  6.         /*  dm: it's very important that system.indexes is never updated as IndexDetails has pointers into it  */  
  7.           uassert(  10156  , str::stream()  <<   " cannot update system collection:  "   <<  ns  <<   "  q:  "   <<  patternOrig  <<   "  u:  "   <<  updateobj , legalClientSystemNS( ns ,  true  ) );  
  8.       }  
  9.        return  _updateObjects( false , ns, updateobj, patternOrig, upsert, multi, logop, debug);  
  10.   }  



    上面方法对要更新的ns进行判断,以避免因更新保留的集合而对系统结构造成损坏,如果一切正常,则调用 _updateObjects方法,如下:

   
  1. // update.cpp 文件第1027行  
  2.   UpdateResult _updateObjects( bool  god,  const   char   * ns,  const  BSONObj &  updateobj, BSONObj patternOrig,  bool  upsert,  bool  multi,  bool  logop , OpDebug &  debug, RemoveSaver *  rs ) {  
  3.       DEBUGUPDATE(  " update:  "   <<  ns  <<   "  update:  "   <<  updateobj  <<   "  query:  "   <<  patternOrig  <<   "  upsert:  "   <<  upsert  <<   "  multi:  "   <<  multi );  
  4.       Client &  client  =  cc();  
  5.        int  profile  =  client.database() -> profile;  
  6.       StringBuilder &  ss  =  debug.str;  
  7.        if  ( logLevel  >   2  )  
  8.           ss  <<   "  update:  "   <<  updateobj.toString();  
  9.        /*  idea with these here it to make them loop invariant for multi updates, and thus be a bit faster for that case  */  
  10.        /*  NOTE: when yield() is added herein, these must be refreshed after each call to yield!  */  
  11.       NamespaceDetails  * d  =  nsdetails(ns);  //  can be null if an upsert...  
  12.       NamespaceDetailsTransient  * nsdt  =   & NamespaceDetailsTransient::get_w(ns);  
  13.        /*  end note  */  
  14.       auto_ptr < ModSet >  mods; // 定义存储修改信息操作(如$inc, $set, $push,)的集合实例  
  15.        bool  isOperatorUpdate  =  updateobj.firstElement().fieldName()[ 0 ]  ==   ' $ ' ;  
  16.        int  modsIsIndexed  =   false ;  //  really the # of indexes  
  17.        if  ( isOperatorUpdate ) {  
  18.            if ( d  &&  d -> indexBuildInProgress ) { // 如果正在构建索引  
  19.                set < string >  bgKeys;  
  20.             d -> inProgIdx().keyPattern().getFieldNames(bgKeys); // 获取当前对象的所有字段(field)信息  
  21.             mods.reset(  new  ModSet(updateobj, nsdt -> indexKeys(),  & bgKeys)); // 为mods绑定操作信息  
  22.           }  
  23.            else  {  
  24.               mods.reset(  new  ModSet(updateobj, nsdt -> indexKeys()) ); // 为mods绑定操作信息;  
  25.           }  
  26.           modsIsIndexed  =  mods -> isIndexed();  
  27.       }  
  28.        // upsert:如果存在就更新,如果不存在就插入  
  29.        if (  ! upsert  &&   ! multi  &&  isSimpleIdQuery(patternOrig)  &&  d  &&   ! modsIsIndexed ) {  
  30.            int  idxNo  =  d -> findIdIndex();  
  31.            if ( idxNo  >=   0  ) {  
  32.               ss  <<   "  byid  " ;  
  33.                // 根据id更新记录信息  
  34.                return  _updateById(isOperatorUpdate, idxNo, mods. get (), profile, d, nsdt, god, ns, updateobj, patternOrig, logop, debug);  
  35.           }  
  36.       }  
  37.        set < DiskLoc >  seenObjects;  
  38.        int  numModded  =   0 ;  
  39.        long   long  nscanned  =   0 ;  
  40.       MatchDetails details;  
  41.        // 构造“更新操作”实例对象并用其构造游标操作(符)实例  
  42.       shared_ptr <  MultiCursor::CursorOp  >  opPtr(  new  UpdateOp( mods. get ()  &&  mods -> hasDynamicArray() ) );  
  43.        // 构造MultiCursor查询游标(参见其构造方法中的 nextClause()语句)  
  44.       shared_ptr <  MultiCursor  >  c(  new  MultiCursor( ns, patternOrig, BSONObj(), opPtr,  true  ) );  
  45.       auto_ptr < ClientCursor >  cc;  
  46.        while  ( c -> ok() ) { // 遍历(下面的c->advance()调用)游标指向的记录信息  
  47.           nscanned ++ ;  
  48.            bool  atomic  =  c -> matcher() -> docMatcher().atomic();  
  49.            //  并将其与更新操作中的条件进行匹配  
  50.            if  (  !  c -> matcher() -> matches( c -> currKey(), c -> currLoc(),  & details ) ) {  
  51.               c -> advance(); // 将游标跳转到下一条记录  
  52.                if  ( nscanned  %   256   ==   0   &&   !  atomic ) {  
  53.                    if  ( cc. get ()  ==   0  ) {  
  54.                       shared_ptr <  Cursor  >  cPtr  =  c;  
  55.                       cc.reset(  new  ClientCursor( QueryOption_NoCursorTimeout , cPtr , ns ) );  
  56.                   }  
  57.                    if  (  !  cc -> yield () ) {  
  58.                       cc.release();  
  59.                        //  TODO should we assert or something?  
  60.                        break ;  
  61.                   }  
  62.                    if  (  ! c -> ok() ) {  
  63.                        break ;  
  64.                   }  
  65.               }  
  66.                continue ;  
  67.           }  
  68.           Record  * r  =  c -> _current(); // 游标当前所指向的记录  
  69.           DiskLoc loc  =  c -> currLoc(); // 游标当前所指向的记录所在地址  
  70.            //  TODO Maybe this is unnecessary since we have seenObjects  
  71.            if  ( c -> getsetdup( loc ) ) { // 判断当前记录是否是重复  
  72.               c -> advance();  
  73.                continue ;  
  74.           }  
  75.           BSONObj js(r);  
  76.           BSONObj pattern  =  patternOrig;  
  77.            if  ( logop ) { // 记录日志  
  78.               BSONObjBuilder idPattern;  
  79.               BSONElement id;  
  80.                //  NOTE: If the matching object lacks an id, we'll log  
  81.                //  with the original pattern.  This isn't replay-safe.  
  82.                //  It might make sense to suppress the log instead  
  83.                //  if there's no id.  
  84.                if  ( js.getObjectID( id ) ) {  
  85.                   idPattern.append( id );  
  86.                   pattern  =  idPattern.obj();  
  87.               }  
  88.                else  {  
  89.                   uassert(  10157  ,   " multi-update requires all modified objects to have an _id "  ,  !  multi );  
  90.               }  
  91.           }  
  92.            if  ( profile )  
  93.               ss  <<   "  nscanned: "   <<  nscanned;  
  94.           ......     
  95.           uassert(  10158  ,   " multi update only works with $ operators "  ,  !  multi );  
  96.       // 查看更新记录操作的时间戳,本人猜测这么做可能因为mongodb会采用最后更新时间戳解决分布式系统  
  97.       // 一致性的问题, 也就是通常使用的Last write wins准则,有关信息可参见这篇文章:  
  98.       // http://blog.mongodb.org/post/520888030/on-distributed-consistency-part-5-many-writer  
  99.           BSONElementManipulator::lookForTimestamps( updateobj );  
  100.           checkNoMods( updateobj );  
  101.            // 更新记录  
  102.           theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , updateobj.objdata(), updateobj.objsize(), debug, god);  
  103.            if  ( logop ) { // 记录日志操作  
  104.               DEV  if ( god ) log()  <<   " REALLY?? "   <<  endl;  //  god doesn't get logged, this would be bad.  
  105.               logOp( " u " , ns, updateobj,  & pattern );  
  106.           }  
  107.            return  UpdateResult(  1  ,  0  ,  1  ); // 返回操作结果  
  108.       }  
  109.        if  ( numModded )  
  110.            return  UpdateResult(  1  ,  1  , numModded );  
  111.       ......  
  112.        return  UpdateResult(  0  ,  0  ,  0  );  
  113.   }  
 



     上面的代码主要执行构造更新消息中的查询条件(selector)游标,并将“游标指向”的记录遍历出来与查询条件进行匹配,如果匹配命中,则进行更 新。(有关游标的构造和继承实现体系,mongodb做的有些复杂,很难一句说清,我会在本系列后面另用篇幅进行说明)
    
    注意上面代码段中的这行代码:

  
  1. theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , updateobj.objdata(), updateobj.objsize(), debug, god);  
 

    该方法会执行最终更新操作,其定义如下:

  1. // pdfile.cpp 文件934行  
  2.        const  DiskLoc DataFileMgr::updateRecord(  
  3.          const   char   * ns,  
  4.         NamespaceDetails  * d,  
  5.         NamespaceDetailsTransient  * nsdt,  
  6.         Record  * toupdate,  const  DiskLoc &  dl,  
  7.          const   char   * _buf,  int  _len, OpDebug &  debug,   bool  god) {  
  8.         StringBuilder &  ss  =  debug.str;  
  9.         dassert( toupdate  ==  dl.rec() );  
  10.         BSONObj objOld(toupdate);  
  11.         BSONObj objNew(_buf);  
  12.         DEV assert( objNew.objsize()  ==  _len );  
  13.         DEV assert( objNew.objdata()  ==  _buf );  
  14.          // 如果_buf中不包含_id,但要更新的记录(toupdate)有_id  
  15.          if (  ! objNew.hasElement( " _id " )  &&  objOld.hasElement( " _id " ) ) {  
  16.              /*  add back the old _id value if the update removes it.  Note this implementation is slow 
  17.                (copies entire object multiple times), but this shouldn't happen often, so going for simple 
  18.                code, not speed. 
  19.              */  
  20.             BSONObjBuilder b;  
  21.             BSONElement e;  
  22.             assert( objOld.getObjectID(e) ); // 获取对象objOld的ID并绑定到e  
  23.             b.append(e);  //  为了最好的性能,先放入_id  
  24.             b.appendElements(objNew);  
  25.             objNew  =  b.obj();  
  26.         }  
  27.          /* 重复key检查 */  
  28.         vector < IndexChanges >  changes;  
  29.          bool  changedId  =   false ;  
  30.          // 获取要修改的索引信息(包括要移除和添加的index key,并将结果返回给changes)  
  31.         getIndexChanges(changes,  * d, objNew, objOld, changedId);  
  32.          // 断言是否要修改_id索引  
  33.         uassert(  13596  , str::stream()  <<   " cannot change _id of a document old: "   <<  objOld  <<   "  new: "   <<  objNew ,  !  changedId );  
  34.         dupCheck(changes,  * d, dl); // 重复key检查,如果重复则通过断言终止当前程序  
  35.          // 如果要更新的记录比最终要插入的记录尺寸小  
  36.          if  ( toupdate -> netLength()  <  objNew.objsize() ) {  
  37.              //  如不合适,则重新分配  
  38.             uassert(  10003  ,  " failing update: objects in a capped ns cannot grow " ,  ! (d  &&  d -> capped));  
  39.             d -> paddingTooSmall();  
  40.              if  ( cc().database() -> profile )  
  41.                 ss  <<   "  moved  " ;  
  42.            // 删除指定的记录(record),删除操作详见我的这篇文章:  
  43.            // http://www.cnblogs.com/daizhj/archive/2011/04/06/mongodb_delete_recode_source_code.html  
  44.             deleteRecord(ns, toupdate, dl);  
  45.              // 插入新的BSONObj信息,插入操作详见我的这篇文章:  
  46.              // http://www.cnblogs.com/daizhj/archive/2011/03/30/1999699.html  
  47.              return  insert(ns, objNew.objdata(), objNew.objsize(), god);  
  48.         }  
  49.         nsdt -> notifyOfWriteOp();  
  50.         d -> paddingFits();  
  51.          /*  如果有要修改的索引  */  
  52.         {  
  53.             unsigned keyUpdates  =   0 ;  
  54.              int  z  =  d -> nIndexesBeingBuilt(); // 获取索引(包括正在构建)数  
  55.              for  (  int  x  =   0 ; x  <  z; x ++  ) {  
  56.                 IndexDetails &  idx  =  d -> idx(x);  
  57.                  // 遍历当前更新记录要修改(移除)的索引键信息  
  58.                  for  ( unsigned i  =   0 ; i  <  changes[x].removed.size(); i ++  ) {  
  59.                      try  {  
  60.                          // 移除当前记录在索引b树中相应信息(索引键)  
  61.                         idx.head.btree() -> unindex(idx.head, idx,  * changes[x].removed[i], dl);  
  62.                     }  
  63.                      catch  (AssertionException & ) {  
  64.                         ss  <<   "  exception update unindex  " ;  
  65.                         problem()  <<   "  caught assertion update unindex  "   <<  idx.indexNamespace()  <<  endl;  
  66.                     }  
  67.                 }  
  68.                 assert(  ! dl.isNull() );  
  69.                  // 获取指定名称(key)下的子对象  
  70.                 BSONObj idxKey  =  idx.info.obj().getObjectField( " key " );  
  71.                 Ordering ordering  =  Ordering::make(idxKey); // 生成排序方式  
  72.                 keyUpdates  +=  changes[x].added.size();  
  73.                   
  74.                  // 遍历当前更新记录要修改(插入)的索引键信息  
  75.                  for  ( unsigned i  =   0 ; i  <  changes[x].added.size(); i ++  ) {  
  76.                      try  {  
  77.                          // 之前做了dupCheck()操作,所以这里不用担心重复key的问题  
  78.                          // 在b树中添加索引键信息,有关该方法的定义参见我的这篇文章  
  79.                          // http://www.cnblogs.com/daizhj/archive/2011/03/30/1999699.html  
  80.                         idx.head.btree() -> bt_insert(  
  81.                             idx.head,  
  82.                             dl,  * changes[x].added[i], ordering,  /* dupsAllowed */ true , idx);  
  83.                     }  
  84.                      catch  (AssertionException &  e) {  
  85.                         ss  <<   "  exception update index  " ;  
  86.                         problem()  <<   "  caught assertion update index  "   <<  idx.indexNamespace()  <<   "   "   <<  e  <<  endl;  
  87.                     }  
  88.                 }  
  89.             }  
  90.              if ( keyUpdates  &&  cc().database() -> profile )  
  91.                 ss  <<   ' /n '   <<  keyUpdates  <<   "  key updates  " ;  
  92.         }  
  93.          //   update in place  
  94.          int  sz  =  objNew.objsize();  
  95.          // 将新修改的记录信息复制到旧记录(toupdate)所在位置  
  96.         memcpy(getDur().writingPtr(toupdate -> data, sz), objNew.objdata(), sz);  
  97.          return  dl;  
  98.     }   



    上面代码段主要先对B树索引进行修改(这里采用先移除再重建方式),之后直接更新旧记录在内存中的数据,最终完成了记录的更新操作。

    最后,用一张时序图回顾一下更新记录时mongodb服务端代码的执行流程:

     



    好了,今天的内容到这里就告一段落了,在接下来的文章中,将会介绍Mongodb的游标(cursor)设计体系和实现方式。



    参考链接:
    http://www.cnblogs.com/daizhj/archive/2011/03/30/1999699.html
    http://www.cnblogs.com/daizhj/archive/2011/04/06/mongodb_delete_recode_source_code.html
    http://www.cnblogs.com/daizhj/archive/2011/04/02/2003335.html
    http://blog.mongodb.org/post/520888030/on-distributed-consistency-part-5-many-writer


    原文链接: http://www.cnblogs.com/daizhj/archive/2011/04/08/mongodb_update_recode_source_code.html

    作者: daizhj, 代震军    
    微博: http://t.sina.com.cn/daizhj 
    Tags: mongodb,c++,source code

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值