mongodb源码分析(十二)数据的更新

来源:互联网 发布:彩票 知乎 编辑:程序博客网 时间:2024/05/30 20:07

        相对于删除操作,更新操作复杂得多,因为其操作很多,mongodb提供了很多更新的操作符,另外还要考虑到更

新时如果原来的数据doc空间不够还得删除原来的doc再添加新的doc,相当于做了两次操作,这里的过程同样会影

响collection中所有的索引.下面来看代码吧,更新操作的入口为:

    void receivedUpdate(Message& m, CurOp& op) {        DbMessage d(m);        const char *ns = d.getns();        op.debug().ns = ns;        int flags = d.pullInt();        BSONObj query = d.nextJsObj();        BSONObj toupdate = d.nextJsObj();        bool upsert = flags & UpdateOption_Upsert;        bool multi = flags & UpdateOption_Multi;        bool broadcast = flags & UpdateOption_Broadcast;        op.debug().query = query;        op.setQuery(query);        PageFaultRetryableSection s;        while ( 1 ) {            try {                Lock::DBWrite lk(ns);                                // void ReplSetImpl::relinquish() uses big write lock so                 // this is thus synchronized given our lock above.                uassert( 10054 ,  "not master", isMasterNs( ns ) );//不是master不能执行更新                // if this ever moves to outside of lock, need to adjust check Client::Context::_finishInit                if ( ! broadcast && handlePossibleShardedMessage( m , 0 ) )                    return;                Client::Context ctx( ns );//实际的更新部分                UpdateResult res = updateObjects(ns, toupdate, query, upsert, multi, true, op.debug() );                lastError.getSafe()->recordUpdate( res.existing , res.num , res.upserted ); // for getlasterror                break;            }            catch ( PageFaultException& e ) {//要更新的数据不在内存,让操作系统加载其到内存                e.touch();            }        }    }
        updateObjects做了基本的检查后直接调用了_updateObjects,所以跳过updateObjects函数

直接分析_updateObjects. receiveUpdate->updateObjects->_updateObjects.

    UpdateResult _updateObjects( bool su,const char* ns,const BSONObj& updateobj,const BSONObj& patternOrig,bool upsert,                                 bool multi,bool logop ,OpDebug& debug,RemoveSaver* rs,bool fromMigrate,const QueryPlanSelectionPolicy& planPolicy ) {        Client& client = cc();        int profile = client.database()->profile;        debug.updateobj = updateobj;        // The idea with these here it to make them loop invariant for        // multi updates, and thus be a bit faster for that case.  The        // pointers may be left invalid on a failed or terminal yield        // recovery.        NamespaceDetails* d = nsdetails(ns); // can be null if an upsert...        NamespaceDetailsTransient* nsdt = &NamespaceDetailsTransient::get(ns);        auto_ptr<ModSet> mods;        bool isOperatorUpdate = updateobj.firstElementFieldName()[0] == '$';        int modsIsIndexed = false; // really the # of indexes        if ( isOperatorUpdate ) {//根据更新操作提取具体的更新符以及数据建立更新操作结构,比如update={$set:{a:1},$inc{b:1}}            if( d && d->indexBuildInProgress ) {//那么建立两个mod,第一个操作符为$set,操作对象为{a:1},第二个操作符为$inc,                set<string> bgKeys;             //操作对象为{b:1}                 d->inProgIdx().keyPattern().getFieldNames(bgKeys);                mods.reset( new ModSet(updateobj, nsdt->indexKeys(), &bgKeys) );            }            else {                mods.reset( new ModSet(updateobj, nsdt->indexKeys()) );            }            modsIsIndexed = mods->isIndexed();//修改可能变更到索引,后面则需要同时更新索引才行        }//单id查询的更新比如说pattern={_id:1},updateobj={$inc:{x:1}},并且这里x不为索引        if( planPolicy.permitOptimalIdPlan() && !multi && isSimpleIdQuery(patternOrig) && d &&           !modsIsIndexed ) {            int idxNo = d->findIdIndex();            if( idxNo >= 0 ) {                debug.idhack = true;//updateById函数和后面的操作流程差不多,这里就不分析了,看后面的分析                UpdateResult result = _updateById( isOperatorUpdate,idxNo,mods.get(),profile,d,nsdt,su,ns,                                                   updateobj,patternOrig,logop,debug,fromMigrate);                if ( result.existing || ! upsert ) {                    return result;                }                else if ( upsert && ! isOperatorUpdate && ! logop) {                    // this handles repl inserts                    checkNoMods( updateobj );                    debug.upsert = true;                    BSONObj no = updateobj;                    theDataFileMgr.insertWithObjMod(ns, no, su);                    return UpdateResult( 0 , 0 , 1 , no );                }            }        }        int numModded = 0;        debug.nscanned = 0;        shared_ptr<Cursor> c =            NamespaceDetailsTransient::getCursor( ns, patternOrig, BSONObj(), planPolicy );        d = nsdetails(ns);        nsdt = &NamespaceDetailsTransient::get(ns);        bool autoDedup = c->autoDedup();        if( c->ok() ) {            set<DiskLoc> seenObjects;            MatchDetails details;            auto_ptr<ClientCursor> cc;            do {                if ( cc.get() == 0 &&//抛出异常在外捕获让系统将数据加载进内存                     client.allowedToThrowPageFaultException() &&                     ! c->currLoc().isNull() &&                     ! c->currLoc().rec()->likelyInPhysicalMemory() ) {                    throw PageFaultException( c->currLoc().rec() );                }                debug.nscanned++;                if ( mods.get() && mods->hasDynamicArray() ) {                    // The Cursor must have a Matcher to record an elemMatchKey.  But currently                    // a modifier on a dynamic array field may be applied even if there is no                    // elemMatchKey, so a matcher cannot be required.                    //verify( c->matcher() );                    details.requestElemMatchKey();                }                if ( !c->currentMatches( &details ) ) {                    c->advance();                    continue;                }   //match                Record* r = c->_current();                DiskLoc loc = c->currLoc();                if ( c->getsetdup( loc ) && autoDedup ) {//首先找到的loc肯定c->getsetdup()返回false,第二次                    c->advance();//更新同样的内容时c->getsetdup返回true,这是由于muti index产生的                    continue;//所以第二次要跳过这一条记录,这种情况不会出现于basicCursor,因为其总是返回false                }                BSONObj js = BSONObj::make(r);                BSONObj pattern = patternOrig;                /* look for $inc etc.  note as listed here, all fields to inc must be this type, you can't set some                    regular ones at the moment. */                if ( isOperatorUpdate ) {                    if ( multi ) {                        // go to next record in case this one moves                        c->advance();                        // Update operations are deduped for cursors that implement their own                        // deduplication.  In particular, some geo cursors are excluded.                        if ( autoDedup ) {                            if ( seenObjects.count( loc ) ) {//判断这个loc是否已经存在,第一次肯定是                                continue;//不存在的,所以总是返回false                            }                            // SERVER-5198 Advance past the document to be modified, provided                            // deduplication is enabled, but see SERVER-5725.                            while( c->ok() && loc == c->currLoc() ) {//如果遍历过程中始终指向了                                c->advance();//该loc,则一直advance,直到跳过该loc                            }                        }                    }                    const BSONObj& onDisk = loc.obj();                    ModSet* useMods = mods.get();                    bool forceRewrite = false;//需要说明的是这里的".$"符号只是替换为match时array当时的位置//所以这里有一个问题,如果我有一条数据为{x:[1,2,3,4,5],y:[6,7,8,9,10]}//当我调用update({x:1,y:9},{$inc:{x.$:1}},false,false)本意是更新x的第一个位置//数据,但是实际上得到的结果是{x:[1,2,3,5,5],y:[6,7,8,9,10]},这算一个bug吗???                    auto_ptr<ModSet> mymodset;//这里是存在着".$"符号                    if ( details.hasElemMatchKey() && mods->hasDynamicArray() ) {                        useMods = mods->fixDynamicArray( details.elemMatchKey() );//这里的elemMatchKey是在查询匹配时                        mymodset.reset( useMods );//设置的,只是简单的将.$替换为elelMatchKey设置的位置                        forceRewrite = true;                    }//建立ModSetState,并且设置每一位更新数据是否可以通过简单替换来达到更新数据的目的如{$set:{a:10}}                    auto_ptr<ModSetState> mss = useMods->prepare( onDisk );//若a本来存在只需要替换其值为10就行了,不涉及到多占内存的情况                    bool willAdvanceCursor = multi && c->ok() && ( modsIsIndexed || ! mss->canApplyInPlace() );                    if ( willAdvanceCursor ) {                        if ( cc.get() ) {                            cc->setDoingDeletes( true );                        }                        c->prepareToTouchEarlierIterate();                    }                    if ( modsIsIndexed <= 0 && mss->canApplyInPlace() ) {//可以直接替换,如上面举的例子                        mss->applyModsInPlace( true );// const_cast<BSONObj&>(onDisk) );                        if ( profile && !multi )                            debug.fastmod = true;                        if ( modsIsIndexed ) {                            seenObjects.insert( loc );                        }                        d->paddingFits();                    }                    else {                        if ( rs )                            rs->goingToDelete( onDisk );//将要修改的部分转存到磁盘上                        BSONObj newObj = mss->createNewFromMods();//要更新的obj不能通过简单的替换某个值来达到更新目的,必须得                        checkTooLarge(newObj);//根据更新值和原来的值重新创建一个新的对象                        DiskLoc newLoc = theDataFileMgr.updateRecord(ns,d,nsdt,r,loc,newObj.objdata(),newObj.objsize(),debug);                        if ( newLoc != loc || modsIsIndexed ){                            // log() << "Moved obj " << newLoc.obj()["_id"] << " from " << loc << " to " << newLoc << endl;                            // object moved, need to make sure we don' get again                            seenObjects.insert( newLoc );                        }                    }                    numModded++;                    if ( ! multi )                        return UpdateResult( 1 , 1 , numModded , BSONObj() );                    if ( willAdvanceCursor )                        c->recoverFromTouchingEarlierIterate();                    getDur().commitIfNeeded();                    continue;                }                BSONElementManipulator::lookForTimestamps( updateobj );                checkNoMods( updateobj );//无操作符数据的更新                theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , updateobj.objdata(), updateobj.objsize(), debug, su);                return UpdateResult( 1 , 0 , 1 , BSONObj() );            } while ( c->ok() );        } // endif        if ( numModded )            return UpdateResult( 1 , 1 , numModded , BSONObj() );        if ( upsert ) {//这里到了插入了,说明查询没查到数据,进入插入操作            if ( updateobj.firstElementFieldName()[0] == '$' ) {                // upsert of an $operation. build a default object                BSONObj newObj = mods->createNewFromQuery( patternOrig );//根据查询语句创建一个obj                checkNoMods( newObj );                debug.fastmodinsert = true;                theDataFileMgr.insertWithObjMod(ns, newObj, su);                return UpdateResult( 0 , 1 , 1 , newObj );            }            checkNoMods( updateobj );            debug.upsert = true;            BSONObj no = updateobj;            theDataFileMgr.insertWithObjMod(ns, no, su);            return UpdateResult( 0 , 0 , 1 , no );        }        return UpdateResult( 0 , isOperatorUpdate , 0 , BSONObj() );    }
       上面的代码可知更新操作如果原对象与更新后的对象占用空间一致并且更新操作不会影响到索引

那么就执行简单的替换,否则就需要调用createNewFromMods函数创建出一个新的对象,并调用

updateRecord去更新实际的数据.下面来看看createNewFromMods函数.

    BSONObj ModSetState::createNewFromMods() {        BSONObjBuilder b( (int)(_obj.objsize() * 1.1) );//很可能需要更多存储空间,初始化更大的空间        createNewObjFromMods( "" , b , _obj );        return _newFromMods = b.obj();    }
    void ModSetState::createNewObjFromMods( const string& root,                                            BSONObjBuilder& builder,                                            const BSONObj& obj ) {        BSONObjIteratorSorted es( obj );        createNewFromMods( root, builder, es, modsForRoot( root ), LexNumCmp( true ) );    }
    void ModSetState::createNewFromMods( const string& root,                                         BSONBuilderBase& builder,                                         BSONIteratorSorted& es,                                         const ModStateRange& modRange,                                         const LexNumCmp& lexNumCmp ) {        ModStateHolder::iterator m = modRange.first;        const ModStateHolder::const_iterator mend = modRange.second;        BSONElement e = es.next();        set<string> onedownseen;        BSONElement prevE;        while ( !e.eoo() && m != mend ) {            if ( duplicateFieldName( prevE, e ) ) {//前一个没有匹配,说明这个域没有被更改到,直接添加到新的对象上                // Just copy through an element with a duplicate field name.                builder.append( e );                prevE = e;                e = es.next();                continue;            }            prevE = e;            string field = root + e.fieldName();            FieldCompareResult cmp = compareDottedFieldNames( m->second->m->fieldName , field ,                                                             lexNumCmp );            switch ( cmp ) {            case LEFT_SUBFIELD: { // Mod is embedded under this element                uassert( 10145,//如这种情况m->fieldName=a.b.c,field=a.b                         str::stream() << "LEFT_SUBFIELD only supports Object: " << field                         << " not: " << e.type() , e.type() == Object || e.type() == Array );                if ( onedownseen.count( e.fieldName() ) == 0 ) {                    onedownseen.insert( e.fieldName() );                    if ( e.type() == Object ) {//深入到下一层创建更深层的对象                        BSONObjBuilder bb( builder.subobjStart( e.fieldName() ) );                        stringstream nr; nr << root << e.fieldName() << ".";                        createNewObjFromMods( nr.str() , bb , e.Obj() );                        bb.done();                    }                    else {                        BSONArrayBuilder ba( builder.subarrayStart( e.fieldName() ) );                        stringstream nr; nr << root << e.fieldName() << ".";                        createNewArrayFromMods( nr.str() , ba , BSONArray( e.embeddedObject() ) );                        ba.done();                    }                    // inc both as we handled both                    e = es.next();                    m++;                }                else {                    massert( 16069 , "ModSet::createNewFromMods - "                            "SERVER-4777 unhandled duplicate field" , 0 );                }                continue;            }            case LEFT_BEFORE: // Mod on a field that doesn't exist                _appendNewFromMods( root , *m->second , builder , onedownseen );                m++;                continue;            case SAME://要更新的域和原本的域是一样的,则直接应用更新,这里添加了更新后的数据                m->second->apply( builder , e );                e = es.next();                m++;                continue;            case RIGHT_BEFORE: // field that doesn't have a MOD                builder.append( e ); // if array, ignore field name                e = es.next();                continue;            case RIGHT_SUBFIELD://比如说e现在是a.b.c,但是m是a,这种情况是不能出现的                massert( 10399 ,  "ModSet::createNewFromMods - RIGHT_SUBFIELD should be impossible" , 0 );                break;            default:                massert( 10400 ,  "unhandled case" , 0 );            }        }        // finished looping the mods, just adding the rest of the elements        while ( !e.eoo() ) {            builder.append( e );  // if array, ignore field name            e = es.next();        }        // do mods that don't have fields already        for ( ; m != mend; m++ ) {//最后添加要更新的域的数据            _appendNewFromMods( root , *m->second , builder , onedownseen );        }    }
        这个函数思想很简单就是根据要更新的值和原本的值创建一条新的doc,但是因为各种回调函数,

以及比较操作所以需要多多调试才能够体会.最后来看看updateRecord函数.

    const DiskLoc DataFileMgr::updateRecord(        const char *ns,        NamespaceDetails *d,        NamespaceDetailsTransient *nsdt,        Record *toupdate, const DiskLoc& dl,        const char *_buf, int _len, OpDebug& debug,  bool god) {        BSONObj objOld = BSONObj::make(toupdate);        BSONObj objNew(_buf);        if( !objNew.hasElement("_id") && objOld.hasElement("_id") ) {//添加一个_id            /* add back the old _id value if the update removes it.  Note this implementation is slow               (copies entire object multiple times), but this shouldn't happen often, so going for simple               code, not speed.            */            BSONObjBuilder b;            BSONElement e;            verify( objOld.getObjectID(e) );            b.append(e); // put _id first, for best performance            b.appendElements(objNew);            objNew = b.obj();        }        /* duplicate key check. we descend the btree twice - once for this check, and once for the actual inserts, further           below.  that is suboptimal, but it's pretty complicated to do it the other way without rollbacks...        */        vector<IndexChanges> changes;        bool changedId = false;//遍历index得到所有的index变化        getIndexChanges(changes, ns, *d, objNew, objOld, changedId);        dupCheck(changes, *d, dl);        if ( toupdate->netLength() < objNew.objsize() ) {//数据长度不够,删除其,然后再插入,这里的数据长度包括了old record //本来的数据长度以及后面留下的padding            // doesn't fit.  reallocate -----------------------------------------------------            uassert( 10003 , "failing update: objects in a capped ns cannot grow", !(d && d->isCapped()));            d->paddingTooSmall();//数据长度不够后调整paddingFit,让下次分配更改的内存避免修改时长度再次不够            deleteRecord(ns, toupdate, dl);            DiskLoc res = insert(ns, objNew.objdata(), objNew.objsize(), god);            if (debug.nmoved == -1) // default of -1 rather than 0                debug.nmoved = 1;            else                debug.nmoved += 1;            return res;        }        nsdt->notifyOfWriteOp();        d->paddingFits();        /* have any index keys changed? */        {            int keyUpdates = 0;//下面数据长度够,所以先删除过时的index和添加新的index            int z = d->nIndexesBeingBuilt();//最后复制数据            for ( int x = 0; x < z; x++ ) {                IndexDetails& idx = d->idx(x);                IndexInterface& ii = idx.idxInterface();                for ( unsigned i = 0; i < changes[x].removed.size(); i++ ) {                        bool found = ii.unindex(idx.head, idx, *changes[x].removed[i], dl);                        if ( ! found ) {                            RARELY warning() << "ns: " << ns << " couldn't unindex key: " << *changes[x].removed[i]                                              << " for doc: " << objOld["_id"] << endl;                        }                }                BSONObj idxKey = idx.info.obj().getObjectField("key");                Ordering ordering = Ordering::make(idxKey);                keyUpdates += changes[x].added.size();                for ( unsigned i = 0; i < changes[x].added.size(); i++ ) {                        /* we did the dupCheck() above.  so we don't have to worry about it here. */                        ii.bt_insert(                            idx.head,                            dl, *changes[x].added[i], ordering, /*dupsAllowed*/true, idx);                    }            }            debug.keyUpdates = keyUpdates;        }        //  update in place        int sz = objNew.objsize();//实际对象的数据复制        memcpy(getDur().writingPtr(toupdate->data(), sz), objNew.objdata(), sz);        return dl;    }

        从update的流程来看整个过程不复杂,但是其中细节很多,需要多多调试才能够明白,其中貌似有个bug

代码中以说明.需要记住的是如果更新后数据的长度超过了原doc能够存储的数据长度,那么更新变更为一次

数据的删除以及一次数据的插入操作.


本文链接: mongodb源码分析(十二)数据的更新

作者: yhjj0108,杨浩








原创粉丝点击