MongoDB 4.0 事务实现快速上手

MongoDB 4.0 事务实现快速上手

原创陈小生网易游戏运维平台

 

MongoDB 4.0 事务实现快速上手_第1张图片

 

陈小生

网易游戏运维工程师,目前主要负责数据库相关的运维工作。

MongoDB 4.0 引入了多文档事务的支持,不过目前仅限于单个复本集(replica sets) 的支持,预计  4.2 可以看到对 Sharding 架构的分布式事务支援。在 4.0 中,MongoDB 为多文档事务提供了 "all-or-nothing" 的原子语义保证,你可以在不同的操作、不同的 documents、不同的 collections,甚至不同的 databases 之间使用多文档事务:

  1. 事务成功提交之前,所有的数据变更在事务之外不可见;

  2. 事务中的任何一项操作失败后,所有的变更会被丢弃,事务回滚;

  3. 支持事务,需要保证使用的是 MongoDB 4.0 或以上版本,并且 featureCompatibilityVersion(https://docs.mongodb.com/manual/reference/command/setFeatureCompatibilityVersion/#dbcmd.setFeatureCompatibilityVersion)需要设置为 4.0。从旧版本升级上来的复本集群可能为 3.6,需要特别注意。可以通过如下命令检查:

db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )

  1. 只有 wiredTiger 引擎支持事务。同时请留意,4.0 已经标记 MMAPv1 为 Deprecated,并且将会在未来的某一版本移除对 MMAPv1 引擎的支持;

Mongo Shell 事务示例

mongodb40:PRIMARY> session = db.getMongo().startSession()
session { "id" : UUID("3bfd3739-caed-4c06-8e61-bfe405658166") }
mongodb40:PRIMARY> trans = session.getDatabase('chenxs').trans
chenxs.trans
mongodb40:PRIMARY> session.startTransaction()
mongodb40:PRIMARY> trans.insert({"a": 3})
WriteResult({ "nInserted" : 1 })
mongodb40:PRIMARY> db.trans.find({a: 3})
mongodb40:PRIMARY> trans.find({a: 3})
{ "_id" : ObjectId("5b5e8579b54570c9fb04ed4c"), "a" : 3 }
mongodb40:PRIMARY> session.abortTransaction()
mongodb40:PRIMARY> trans.find({a: 3})

mongodb40:PRIMARY> session.startTransaction()
mongodb40:PRIMARY> trans.insert({a: 3})
WriteResult({ "nInserted" : 1 })
mongodb40:PRIMARY> db.trans.find({a: 3})
mongodb40:PRIMARY> session.commitTransaction()
mongodb40:PRIMARY> db.trans.find({a: 3})
{ "_id" : ObjectId("5b5e85c3b54570c9fb04ed4d"), "a" : 3 }
mongodb40:PRIMARY>

上面的示例分别演示了一个 abortTransaction 和 commitTransaction 的简单示例,其中 trans 和 db.trans为对同一个 collection chenxs.trans 的操作,我们可以发现:

  1. 所有事务都是 session 相关的,否则相应的操作会被认为是事务之外的操作。如 trans.insert({"a": 3}) 属于相应 session 事务中的操作,db.trans.find({a: 3}) 则属于事务外的操作,因此查不到事务中的提交;

  2. 事务回滚后,事务内、事务外均查不到相应的变更,即所有变更被丢弃;

  3. 事务成功提交后,外部 session 可以查到提交后的变更结果;

session

session 是在 MongoDB 3.6 版本中引入的,session 本质上可以理解为一个「上下文」,为 3.6 版本提供了「因果一致性」和可重试写入,在 4.0 版本中,session 做为事务的基础而存在,我们可以认为,session 的引入就是为多文档事务做准备。

transaction

在 mongo shell 中,

我们使用 Session.startTransAction() 开始一个事务。

session.startTransaction( {
   readConcern: { level:  },
   writeConcern: { w: , j: , wtimeout:  }
} );

该函数主要包括 readConcern 和 writeConcern 两个可选配置项。

readConcern

多文档事务支持 local、majority、snapshot 三个 read concern 设置,其中 snapshot 主要是在 majority 的基础上实现了对因果一致性的保证;请留意:

  1. readConcern 的设置是事务级别的,不能对事务内的操作单独设置 readConcern;

  2. snapshot 设置只在多文档事务中支持,如:session 并不支持设置 readConcern 为 snapshot;

     

    mongodb40:PRIMARY> session = db.getMongo().startSession({readConcern: {"level": "snapshot"}})
    session { "id" : UUID("535f02bf-17ae-48c8-8cb9-cdf89b7c48bc") }
    mongodb40:PRIMARY> session.getDatabase("chenxs")['trans'].find()
    Error: error: {
       "operationTime" : Timestamp(1532930368, 1),
       "ok" : 0,
       "errmsg" : "readConcern level snapshot is only valid in multi-statement transactions",
       "code" : 72,
       "codeName" : "InvalidOptions",
       "$clusterTime" : {
           "clusterTime" : Timestamp(1532930368, 1),
           "signature" : {
               "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
               "keyId" : NumberLong(0)
           }
       }
    }
    mongodb40:PRIMARY>

  3. 如果在多文档事务中配置了 readConcern,则 readPreference 只允许设置为 Primary;

writeConcern

  1. writeConcern 的设置是事务级别的,不能对事务内的操作单独设置 writeConcern;

  2. 多文档事务中,不允许设置 w: 0 模式;

  3. 只有设置了 w: majority 时,readConcern  snapshot 和 majority   才能保证获取到已经在多数节点提交的数据;

主要配置参数

  1. transactionLifetimeLimitSeconds 设备单个事务的最长生命周期,默认为 60 秒,最小值为 1 秒;可通过如下方式进行设置:

db.adminCommand( { setParameter: 1, transactionLifetimeLimitSeconds: 30 } )

  1. maxTransactionLockRequestTimeoutMillis 事务中的锁等待时间,默认为 5 毫秒,如果超过该时间,则事务回滚(aborts);可通过如下方式进行设置:

db.adminCommand( { setParameter: 1, maxTransactionLockRequestTimeoutMillis: 20 } )

  • 当设置为 0 时,表示不等待,如果请求锁失败,则事务立即回滚;

  • 当设置为 -1 时,表示一直等待,直到操作超时;

其它注意事项

  1. 所有事务中的 CRUD 操作,均要求 collection 已经预先存在;

  2. 所有事务均不能对 config、admin、local 三个 database 进行操作;

  3. 事务中不能对 system.* 的 collection 进行写操作(增删改);

  4. 事务内部不能对事务外部创建的游标(cursor)进行 getMore 操作,反之亦然;

  5. 如果在创建或删除 collection 后需要马上进入事务,并且事务中有对原 collection 的操作,则建议在创建或删除时,设置 writeConcern 为 majority

  6. 事务中不能直接使用 Count Operation,需要在 aggregation 中使用 $count   或 $group 来替代,如:

   mongodb40:PRIMARY> session.startTransaction()
   mongodb40:PRIMARY> trans.count()
   2018-07-30T14:31:44.187+0800 E QUERY    [js] Error: count failed: {
       "operationTime" : Timestamp(1532932298, 1),
       "ok" : 0,
       "errmsg" : "Cannot run 'count' in a multi-document transaction. Please see http://dochub.mongodb.org/core/transaction-count for a recommended alternative.",
       "code" : 50851,
       "codeName" : "Location50851",
       "$clusterTime" : {
           "clusterTime" : Timestamp(1532932298, 1),
           "signature" : {
               "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
               "keyId" : NumberLong(0)
           }
       }
   } :
   _getErrorWithCode@src/mongo/shell/utils.js:25:13
   DBQuery.prototype.count@src/mongo/shell/query.js:382:11
   DBCollection.prototype.count@src/mongo/shell/collection.js:1424:12
   @(shell):1:1
   mongodb40:PRIMARY> session.abortTransaction()

   mongodb40:PRIMARY> session.startTransaction()
   mongodb40:PRIMARY> trans.aggregate([{$match: {a: {$gte: 0}}}, {$count: "col_num"}])
   { "col_num" : 12 }
  1. [Important] 使用事务将不可避免的大幅降低服务性能,尽可能避免事务的使用;

  2. [Repeat] 多文档事务目前只支持单复本集群,预计在 4.2 才会开始支持 Sharding 架构;

客户端驱动

请特别留意:所有与事务相关的读写操作,均需与 transaction session 相关联。(参考下方 Python 样例及官方样例)

Language Version
Java 3.8.0
Python 3.7.0
C 1.11.0
C# 2.7
CXX 3.4.x

Python 示例

mc = MongoClient("mongodb://192.168.93.12:27017,192.168.93.18:27017,192.168.93.11:27017/?replicaSet=mongodb40")

with mc.start_session() as session:
    collection = session.client.chenxs.trans
    session.start_transaction()
    collection.insert_one({"a": 14}, session=session)
    result_inside_trans = collection.find_one({"a": 14}, session=session)
    # with transaction session: got a
    print "inside trans before commit, we got: %s" % result_inside_trans
    result_outside_trans = collection.find_one({"a": 14})
    # without transaction session: got none
    print "outside trans before commit, we got: %s" % result_outside_trans
    session.abort_transaction()

参考文档

  1. Transactions

    MongoDB 4.0 事务实现快速上手_第2张图片

  2. Read Concern

    MongoDB 4.0 事务实现快速上手_第3张图片

  3. $count (aggregation)

    MongoDB 4.0 事务实现快速上手_第4张图片

往期精彩

 

MongoDB Change streams 与数据订阅同步

 

人工智障入门

 

网易游戏《荒野行动》《阴阳师》等出海实践-AWS技术峰会演讲实录

 

MySQL Flashback拯救手抖党

 

函数式编程在JavaScript下应用实践

MongoDB 4.0 事务实现快速上手_第5张图片

你可能感兴趣的:(MongoDB 4.0 事务实现快速上手)