0
点赞
收藏
分享

微信扫一扫

mongodb3.6事务

本文基于Mongodb3.6,对于Mongodb上层事务中会让人困惑的几点进行源码层面的分析

mongodb 的写操作(insert/update/delete)提供的“单行一致性”的具体含义,如何做到的?

为何db.coll.count()在宕机崩溃后经常就不准了。

mongodb 查询操作的事务隔离级别。

1.写操作的事务性

Mongodb的数据组织

在了解写操作的事务性之前,需要先了解mongo层的每一个table,是如何与wiredtiger层的table(btree)对应的。mongo层一个最简单的table包含一个 ObjectId(_id) 索引。_id类似于Mysql中主键的概念。

rs1:PRIMARY> db.abc.getIndexes()
[
        {
                "v" : 1,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "test.abc"
        }
]

但是mongo中并不会将_id索引与行内容存放在一起(即没有聚簇索引的概念)。取而代之的,mongodb将索引与数据分开存放,通过RecordId进行间接引用。 举例一张包含两个索引(_id 和 name)的表,在wt层将有三张表与其对应。通过name索引找到行记录的过程为:先通过name->Record的索引找到RecordId,再通过RecordId->RowData的索引找到记录内容。

mongodb3.6事务_mongodb

此外,一个Mongodb实例还包含一张记录对每一行的写操作的表local.oplog.rs, 该表主要用于复制(primary-secondary replication)。每一次(对实例中任何一张表的任何一行的)更新操作,都会产生唯一的一条oplog,记录在local.oplog.rs表里。

2.理解单行事务

mongodb对某一行的写操作,会产生三个动作:

对wt层的数据段btree(上图中的Data Ident)执行写操作

对wt层索引段的每个索引btree执行写操作

对oplog表执行写操作

mongodb的单行事务,说的是:对数据,索引,oplog这三者的更新是原子的。不存在索引段中的某个RecordId,在数据段中找不到,也不存在一条记录的更改被应用,但是没有记录到oplog中, 反之亦然。

从下面的代码可以看到,一个插入操作,更新数据,索引,以及Oplog的过程。

collection_impl.cpp
  Status CollectionImpl::insertDocuments(OperationContext* opCtx)
      Status status = _insertDocuments(opCtx, begin, end, enforceQuota, opDebug); // 更新数据和索引
      getGlobalServiceContext()->getOpObserver()->onInserts(opCtx, ns(), uuid(), begin, end, fromMigrate);    // 更新Oplog
      return Status::OK();
  }

  Status CollectionImpl::_insertDocuments(OperationContext* opCtx)
      _recordStore->insertRecords(opCtx, &records, &timestamps, _enforceQuota(enforceQuota)); // 更新数据
      std::vector bsonRecords;
      int recordIndex = 0;
      for (auto it = begin; it != end; it++) {
          RecordId loc = records[recordIndex++].id;
          BsonRecord bsonRecord = {loc, &(it->doc)};
          bsonRecords.push_back(bsonRecord);
      }
      int64_t keysInserted;
      status = _indexCatalog.indexRecords(opCtx, bsonRecords, &keysInserted);  // 更新所有索引
      return status;
  }

3.单行事务的实现

OperationContext与RecoveryUnit

客户端的每个请求(insert/update/delete/find/getmore),会生成一个唯一的OperationContext记录执行的上下文,OperationContext从请求解析时创建,到请求执行完成时释放。一般情况下,其生命周期等同于一个操作执行的生命周期。OperationContext创建时,会初始化RecoveryUnit。

service_context_d.cpp:288
 std::unique_ptr ServiceContextMongoD::_newOpCtx(Client* client, unsigned opId) {
     auto opCtx = stdx::make_unique(client, opId);
     opCtx->setRecoveryUnit(getGlobalStorageEngine()->newRecoveryUnit(),
                            OperationContext::kNotInUnitOfWork);
     return opCtx;
 }

RecoveryUnit封装了wiredTiger层的事务。RecoveryUnit::_txnOpen 对应于WT层的beginTransaction。 RecoveryUnit::_txnClose封装了WT层的commit_transaction和rollback_transaction。

beginTransaction

wiredtiger_recovery_unit.cpp
 void WiredTigerRecoveryUnit::_txnOpen() {
         invariantWTOK(session->begin_transaction(session, NULL));
     _active = true;
 }

commit/rollback

wiredtiger_recovery_unit.cpp
 void WiredTigerRecoveryUnit::_txnClose(bool commit) {
     if (commit) {
         wtRet = s->commit_transaction(s, NULL);
     } else {
         wtRet = s->rollback_transaction(s, NULL);
         invariant(!wtRet);
     }
     _active = false;
 }

WriteUnitOfWork

WriteUnitOfWork 是事务框架提供给server层,方便执行事务的API。它是对OperationContext和RecoveryUnit的封装。

class WriteUnitOfWork {
    WriteUnitOfWork(OperationContext* opCtx) {
            _opCtx->recoveryUnit()->beginUnitOfWork(_opCtx);
    }   

    ~WriteUnitOfWork() {
            _opCtx->recoveryUnit()->abortUnitOfWork();
    }   
}

server层执行一个写操作的事务:

mongo/db/exec/update.cpp
         WriteUnitOfWork wunit(getOpCtx());
         uassertStatusOK(_collection->insertDocument(getOpCtx(),
                                                     InsertStatement(request->getStmtId(), newObj),
                                                     _params.opDebug,
                                                     enforceQuota,
                                                     request->isFromMigration()));
         wunit.commit();

4.总结

简而言之,对一行记录的更改,涉及到数据,索引,和Oplog三者,在wiredTiger层,这样的更改对应于对多张表的更改。Mongodb通过实现事务框架(RecoveryUnit,OperationContext, WriteUnitOfWork)将细节封装。但归根结底非常简单,依然是教科书般的:

begin_transaction
do writes
end_transaction(commit/rollback)

下图是对上面的代码分析整理的调用层次关系。

mongodb3.6事务_封装_02

举报

相关推荐

0 条评论