在中国,使用Mongoid来尝试使用事务功能

MongoDB 4.x 支持事务。

巷上有各种各样的文章介绍如何通过不同编程语言尝试 MongoDB 的事务,而 Mongoid 作为一个例外也支持事务功能,所以我们可以试一试。

MongoDB的事务处理

最好参考官方文件来了解详细信息。
MongoDB的事务支持以下内容,本文将尝试探讨这些内容。

    1. 提交一个文档

 

    1. 回滚

 

    1. 批量处理多个文档

 

    批量处理不同集合的文档

提交一个文档。

首先,尝试在 rails console 中在事务中创建并提交一个文档。

irb(main):001:0> Item.with_session do |s|
irb(main):002:1* s.start_transaction
irb(main):003:1> Item.create(name: 'item01')
irb(main):004:1> s.commit_transaction
irb(main):005:1> end
MONGODB | [9] mongo:30002 #1 | sandbox.insert | STARTED | {"insert"=>"items", "ordered"=>true, "startTransaction"=>true, "autocommit"=>false, "documents"=>[{"_id"=>BSON::ObjectId('5df8f7f921b6200c6571f5af'), "name"=>"item01"}], "txnNumber"=>#<BSON::Int64:0x00007fd8e229e768 @value=1>, "lsid"=>{"id"=><BSON::Bi...
MONGODB | [9] mongo:30002 | sandbox.insert | SUCCEEDED | 0.001s
MONGODB | [10] mongo:30002 #1 | admin.commitTransaction | STARTED | {"commitTransaction"=>1, "autocommit"=>false, "$clusterTime"=>{"clusterTime"=>#<BSON::Timestamp:0x00007fd8e2350620 @seconds=1576597490, @increment=2>, "signature"=>{"hash"=><BSON::Binary:0x70284742394300 type=generic data=0x0000000000000000...>, "keyI...
MONGODB | [10] mongo:30002 | admin.commitTransaction | SUCCEEDED | 0.001s
=> true

当 with_session 块完成后,处理会开始运行。
默认情况下,事务会在60秒内超时,因此,在 start_transaction 和 commit_transaction 之间插入 sleep,就会得到不同的结果。

irb(main):006:0> Item.with_session do |s|
irb(main):007:1* s.start_transaction
irb(main):008:1> Item.create(name: 'item02')
irb(main):009:1> sleep(90)
irb(main):010:1> s.commit_transaction
irb(main):011:1> end
MONGODB | [11] mongo:30002 #1 | sandbox.insert | STARTED | {"insert"=>"items", "ordered"=>true, "startTransaction"=>true, "autocommit"=>false, "documents"=>[{"_id"=>BSON::ObjectId('5df8fb5221b6200c6571f5b3'), "name"=>"item02"}], "$clusterTime"=>{"clusterTime"=>#<BSON::Timestamp:0x00007fd8f2ac52d8 @seconds=157...
MONGODB | [11] mongo:30002 | sandbox.insert | SUCCEEDED | 0.002s
MONGODB | [12] mongo:30002 #1 | admin.commitTransaction | STARTED | {"commitTransaction"=>1, "autocommit"=>false, "$clusterTime"=>{"clusterTime"=>#<BSON::Timestamp:0x00007fd8f2b96220 @seconds=1576598346, @increment=1>, "signature"=>{"hash"=><BSON::Binary:0x70284880949340 type=generic data=0x0000000000000000...>, "keyI...
MONGODB | [12] mongo:30002 | admin.commitTransaction | FAILED | Transaction 2 has been aborted. (251) | 0.004872s
Traceback (most recent call last):
        2: from (irb):23
        1: from (irb):27:in `block in irb_binding'
Mongo::Error::OperationFailure (Transaction 5 has been aborted. (251) (on mongo:30002, attempt 1))

“回滚”

如果执行abort_transaction操作,就可以进行回滚。

irb(main):001:0> Item.with_session do |s|
irb(main):002:1* s.start_transaction
irb(main):003:1> Item.create(name: 'item03')
irb(main):004:1> s.abort_transaction
irb(main):005:1> end
MONGODB | [9] mongo:30002 #1 | sandbox.insert | STARTED | {"insert"=>"items", "ordered"=>true, "startTransaction"=>true, "autocommit"=>false, "documents"=>[{"_id"=>BSON::ObjectId('5df8fd3f21b6204af471f5af'), "name"=>"item03"}], "txnNumber"=>#<BSON::Int64:0x00007fd8f2757858 @value=1>, "lsid"=>{"id"=><BSON::Bi...
MONGODB | [9] mongo:30002 | sandbox.insert | SUCCEEDED | 0.001s
MONGODB | [10] mongo:30002 #1 | admin.abortTransaction | STARTED | {"abortTransaction"=>1, "autocommit"=>false, "$clusterTime"=>{"clusterTime"=>#<BSON::Timestamp:0x00007fd8c20af2d8 @seconds=1576598846, @increment=1>, "signature"=>{"hash"=><BSON::Binary:0x70284472580280 type=generic data=0x0000000000000000...>, "keyId...
MONGODB | [10] mongo:30002 | admin.abortTransaction | SUCCEEDED | 0.002s
=> true
irb(main):006:0> Item.where(name: 'item03').first
MONGODB | [11] mongo:30002 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item03"}, "sort"=>{"_id"=>1}, "limit"=>1, "singleBatch"=>true, "$clusterTime"=>{"clusterTime"=>#<BSON::Timestamp:0x00007fd8c20af2d8 @seconds=1576598846, @increment=1>, "signature"=>{"hash"=><BSON::Binary:0x7028447...
MONGODB | [11] mongo:30002 | sandbox.find | SUCCEEDED | 0.002s
=> nil

处理多个文件

为了确认事务正在运行,使用Mongoid::Timestamps。

class Item
  include Mongoid::Document
  include Mongoid::Timestamps

  field :name
end

尝试连续创建两个文档(并在它们之间插入睡眠)。

irb(main):001:0> Item.with_session do |s|
irb(main):002:1* s.start_transaction
irb(main):003:1> Item.create(name: 'item01')
irb(main):004:1> sleep(10)
irb(main):005:1> Item.create(name: 'item02')
irb(main):006:1> s.commit_transaction
irb(main):007:1> end
MONGODB | [9] mongo:30002 #1 | sandbox.insert | STARTED | {"insert"=>"items", "ordered"=>true, "startTransaction"=>true, "autocommit"=>false, "documents"=>[{"_id"=>BSON::ObjectId('5df9fd3721b620cf5d71f5af'), "name"=>"item01", "updated_at"=>2019-12-18 10:19:35 UTC, "created_at"=>2019-12-18 10:19:35 UTC}], "tx...
MONGODB | [9] mongo:30002 | sandbox.insert | SUCCEEDED | 0.002s
MONGODB | [10] mongo:30002 #1 | sandbox.insert | STARTED | {"insert"=>"items", "ordered"=>true, "autocommit"=>false, "documents"=>[{"_id"=>BSON::ObjectId('5df9fd4121b620cf5d71f5b0'), "name"=>"item02", "updated_at"=>2019-12-18 10:19:45 UTC, "created_at"=>2019-12-18 10:19:45 UTC}], "$clusterTime"=>{"clusterTime...
MONGODB | [10] mongo:30002 | sandbox.insert | SUCCEEDED | 0.002s
MONGODB | [11] mongo:30002 #1 | admin.commitTransaction | STARTED | {"commitTransaction"=>1, "autocommit"=>false, "$clusterTime"=>{"clusterTime"=>#<BSON::Timestamp:0x00007fd8d24b0520 @seconds=1576664379, @increment=1>, "signature"=>{"hash"=><BSON::Binary:0x70284608897460 type=generic data=0x0000000000000000...>, "keyI...
MONGODB | [11] mongo:30002 | admin.commitTransaction | SUCCEEDED | 0.002s
=> true

然后运行上述命令,并在mongo shell中查看文档。

mongo-set:PRIMARY> new Date()
ISODate("2019-12-18T10:19:39.393Z")
mongo-set:PRIMARY> db.items.find()
mongo-set:PRIMARY> db.items.find()
{ "_id" : ObjectId("5df9fd3721b620cf5d71f5af"), "name" : "item01", "updated_at" : ISODate("2019-12-18T10:19:35.398Z"), "created_at" : ISODate("2019-12-18T10:19:35.398Z") }
{ "_id" : ObjectId("5df9fd4121b620cf5d71f5b0"), "name" : "item02", "updated_at" : ISODate("2019-12-18T10:19:45.411Z"), "created_at" : ISODate("2019-12-18T10:19:45.411Z") }

在第一个created_at之后,执行了第一次db.items.find(),但此时发现没有保存任何一件。

4. 批量处理不同集合中的文档

准备另一个模型,尝试处理不同的收集案例。

class Owner
  include Mongoid::Document
  include Mongoid::Timestamps

  field :name
end

Item 模型和 Owner 模型之间没有关系。请按照前面的步骤执行。

irb(main):020:0> Item.with_session do |s|
irb(main):021:1* s.start_transaction
irb(main):022:1> Owner.create(name: 'test owner')
irb(main):023:1> sleep(20)
irb(main):024:1> Item.create(name: 'item03')
irb(main):025:1> s.commit_transaction
irb(main):026:1> end
MONGODB | [14] mongo:30002 #1 | sandbox.insert | STARTED | {"insert"=>"owners", "ordered"=>true, "startTransaction"=>true, "autocommit"=>false, "documents"=>[{"_id"=>BSON::ObjectId('5df9ff8021b620cf5d71f5b2'), "name"=>"test owner", "updated_at"=>2019-12-18 10:29:20 UTC, "created_at"=>2019-12-18 10:29:20 UTC}]...
MONGODB | [14] mongo:30002 | sandbox.insert | SUCCEEDED | 0.002s
MONGODB | [15] mongo:30002 #1 | sandbox.insert | STARTED | {"insert"=>"items", "ordered"=>true, "autocommit"=>false, "documents"=>[{"_id"=>BSON::ObjectId('5df9ff9421b620cf5d71f5b3'), "name"=>"item03", "updated_at"=>2019-12-18 10:29:40 UTC, "created_at"=>2019-12-18 10:29:40 UTC}], "$clusterTime"=>{"clusterTime...
MONGODB | [15] mongo:30002 | sandbox.insert | SUCCEEDED | 0.002s
MONGODB | [16] mongo:30002 #1 | admin.commitTransaction | STARTED | {"commitTransaction"=>1, "autocommit"=>false, "$clusterTime"=>{"clusterTime"=>#<BSON::Timestamp:0x00007fd8d23f1dc8 @seconds=1576664979, @increment=1>, "signature"=>{"hash"=><BSON::Binary:0x70284608507340 type=generic data=0x0000000000000000...>, "keyI...
MONGODB | [16] mongo:30002 | admin.commitTransaction | SUCCEEDED | 0.002s
=> true

执行上述操作后,获取当前时间以及两个集合的值。

mongo-set:PRIMARY> new Date()
ISODate("2019-12-18T10:29:25.613Z")
mongo-set:PRIMARY> db.items.find()
{ "_id" : ObjectId("5df9fd3721b620cf5d71f5af"), "name" : "item01", "updated_at" : ISODate("2019-12-18T10:19:35.398Z"), "created_at" : ISODate("2019-12-18T10:19:35.398Z") }
{ "_id" : ObjectId("5df9fd4121b620cf5d71f5b0"), "name" : "item02", "updated_at" : ISODate("2019-12-18T10:19:45.411Z"), "created_at" : ISODate("2019-12-18T10:19:45.411Z") }
mongo-set:PRIMARY> db.owners.find()
mongo-set:PRIMARY>
mongo-set:PRIMARY> db.items.find()
{ "_id" : ObjectId("5df9fd3721b620cf5d71f5af"), "name" : "item01", "updated_at" : ISODate("2019-12-18T10:19:35.398Z"), "created_at" : ISODate("2019-12-18T10:19:35.398Z") }
{ "_id" : ObjectId("5df9fd4121b620cf5d71f5b0"), "name" : "item02", "updated_at" : ISODate("2019-12-18T10:19:45.411Z"), "created_at" : ISODate("2019-12-18T10:19:45.411Z") }
{ "_id" : ObjectId("5df9ff9421b620cf5d71f5b3"), "name" : "item03", "updated_at" : ISODate("2019-12-18T10:29:40.162Z"), "created_at" : ISODate("2019-12-18T10:29:40.162Z") }
mongo-set:PRIMARY> db.owners.find()
{ "_id" : ObjectId("5df9ff8021b620cf5d71f5b2"), "name" : "test owner", "updated_at" : ISODate("2019-12-18T10:29:20.158Z"), "created_at" : ISODate("2019-12-18T10:29:20.158Z") }

在交易的一系列处理完成之前,我们可以知道两个条目都没有被写入。
同时我们也可以知道创建owners的操作是在mongo shell中确认当前时间之前发生的。