MongoDB 单文档原子操作的实现

transaction的支持在 MongoDB 里面是逐渐进行的, 从3.2版本开始支持单文档的原子操作, 这是后续的多文档原子操作, 以及4.0 的副本集的事务, 以及后续的计划推出的4.2的分片的事务操作的基础。本文着重描述一下单文档的原子操作。

原子性: 就是一个操作要么成功, 要么失败。具体来说, 就是一个插入、更新或者删除操作, 应该只有commit或者rollback两种结果中的一种。对于每一个操作, 都有一个上下文, 来记录相关的信息,OperationContext以及子类OperationContextImpl就是用来记录能够进行的操作, 以及相关的状态信息。

class OperationContext {
protected:
    RecoveryUnitState _ruState = kNotInUnitOfWork;

private:
    friend class WriteUnitOfWork;
    Client* const _client;
    const unsigned int _opId;

    // Not owned.
    Locker* const _locker;

    // Follows the values of ErrorCodes::Error. The default value is 0 (OK), which means the
    // operation is not killed. If killed, it will contain a specific code. This value changes only
    // once from OK to some kill code.
    AtomicWord<ErrorCodes::Error> _killCode{ErrorCodes::OK};

    WriteConcernOptions _writeConcern;
};
class OperationContextImpl : public OperationContext {

private:
    std::unique_ptr<RecoveryUnit> _recovery;
    bool _writesAreReplicated;
};

从OperationContext看出, 它的主要实现类是RecoveryUnit, 该类是在storage下面定义的, 进行不同存储引擎下的change操作的commit与rollbackup行为。要保持某个操作的原子性, 最只要的实现就在RecoveryUnit的函数。对于每一个change操作, 要从RecoveryUnit:Chnage来继承和定义其commit和rollback行为。定义好一个Change类之后, 可以通过registerChange函数, 将该Change添加到这个RecoveryUnit里面的一个保存此次操作的所有的change的集合内。比如, 在WiredTiger下面, 我们定义了class WiredTigerRecoveryUnit。

class RecoveryUnit {
    class Change {
    public:
        virtual ~Change() {}
        virtual void rollback() = 0;
        virtual void commit() = 0;
    };
    virtual void registerChange(Change* change) = 0;
    virtual void beginUnitOfWork(OperationContext* opCtx) = 0;
    virtual void commitUnitOfWork() = 0;
    virtual void abortUnitOfWork() = 0;
 }
 class WiredTigerRecoveryUnit final : public RecoveryUnit {
 private:
    WiredTigerSessionCache* _sessionCache;  // not owned
    WiredTigerSession* _session;            // owned, but from pool
    bool _areWriteUnitOfWorksBanned = false;
    bool _inUnitOfWork;
    bool _active;
    
    // 记录RecoveryUnit对应的snapshotId
    uint64_t _mySnapshotId;
    bool _everStartedWrite;
    Timer _timer;
    RecordId _oplogReadTill;
    bool _readFromMajorityCommittedSnapshot = false;
    SnapshotName _majorityCommittedSnapshot = SnapshotName::min();

    // 包含operationContext里面的所有的Change
    typedef OwnedPointerVector<Change> Changes;
    Changes _changes;
};

如上所示, RecoveryUnit主要进行beginUnitOfWork, commitUnitOfWork以及abortUnitOfWork。其用法为:

beginUnitOfWork
//insert or update
if normal {
   commitUnitOfWork
}else {
    abortUnitOfWork
 }

是不是很类似常见的关系型数据库的事务操作?

原子性读操作

在分布式下同下, 并发的读写是很常见的情形, MongoDB是如何避免脏读的?
在MongoDB里面, 通过snapshot, 来记录下一个个的数据库瞬间的状态。通过SnapshotThread类, 周期性的创建一系列的snapshot, 这些snapshot, 通过WiredTigerSnapshotManager来管理。WiredTigerSnapshotManager::_committedSnapshot来记录已经提交的snapshot 的ID, 在这个ID之前的snapshot是可读的, 并且是可以被清理的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值