Mongodb中的 原子性 隔离性

读写锁

Mongodb使用读写锁来来控制并发操做:css

当进行读操做的时候会加读锁,这个时候其余读操做能够也得到读锁。可是不能或者写锁。html

当进行写操做的时候会加写锁,这个时候不能进行其余的读操做和写操做。mongodb

因此按照这个道理,是不会出现同时修改同一个文档(如执行++操做)致使数据出错的状况。数据库

并且按照这个道理,由于写操做会阻塞读操做,因此是不会出现脏读的。安全

可是mongodb在分片和复制集的时候会产生脏读,后面在研究。网络

读写锁的粒度:

在2.2以前的版本,一个mongodb实例一个写锁,多个读锁,在2.2-3.0的版本,一个数据库一个写锁,多个读锁,在3.0以后的版本,WiredTiger提供了文档(不是集合)级别的锁。并发

 

findAndModify

 

db.collection.findAndModify({ app

query: <document>, ide

sort: <document>, 高并发

remove: <boolean>,

update: <document>,

new: <boolean>,

fields: <document>,

upsert: <boolean>,

bypassDocumentValidation: <boolean>,

writeConcern: <document>,

collation: <document>

});

documentsort:

。可选的。以此参数指定的排序顺序修改第一个文档。

document

document。可选的。 要返回的字段的子集。 如:fields: {<field1>: 1, <field2>: 1, ... }

book = {

_id: 123456789,

title: "MongoDB: The Definitive Guide",

author: [ "Kristina Chodorow", "Mike Dirolf" ],

published_date: ISODate("2010-09-24"),

pages: 216,

language: "English",

publisher_id: "oreilly",

available: 3,

checkout: [ { by: "joe", date: ISODate("2012-10-15") } ]

}

你可使用 db.collection.findAndModify() 方法来判断书籍是否可结算并更新新的结算信息。

在同一个文档中嵌入的 available  checkout 字段来确保这些字段是同步更新的:

db.books.findAndModify ( {

query: {

_id: 123456789,

available: { $gt: 0 }

},

update: {

$inc: { available: -1 },

$push: { checkout: { by: "abc", date: new Date() } }

}

} )

 

执行多个写入操做

 

首先,原则上说Mongdb没有事务的概念。

事务有ACID的概念,好比原子性,一个事务要么所有成功,要么所有失败。

如,考虑一个转帐的业务,从A转帐100到B,将分为两步:

A = A - 100;

B = B + 100;

在Mongdb中,若是A = A - 100;执行完,将会直接入库生效,没有回滚段的概念,因此若是此时B = B + 100;出现了问题,是不能回滚上一步A的操做的。

Mongdb在执行多个更新的时候是没有原子性的。

 

一个写入操做更新了多个文档:

当单个写入操做修改多个文档时,每一个文档的修改是原子的,但整个操做不是原子的,而其余操做可能会交错。 可是,您可使用$ isolation操做符隔离影响多个文档的单个写入操做。

当Mongodb执行影响多个文档的写入操做的时候,若是在中间某一个文档出现了错误,那么不会回滚以前的提交。以前的提交已经入库了。

MongoDB不隔离多文档写入操做,具备如下特色:

非时间点读操做。其中一假设读取操做在时间t1开始,并开始读取文档。写操做而后在稍后的时间t2向个文档提交更新。读操做可能会看到写操做的更新版本,所以读取操做没有时间点的概念。

读取可能会丢失在读取操做过程当中更新的匹配文档。

使用$ isolation来保证隔离性:

使用$isolated操做符能够保证单个写入操做修改多个文档的时候不被交错。

$isolated实际上是在整个数据库(Mongodb的手册对这点说明不清楚,也多是在集合层面加独占锁,可是有一点文档中是说明的,不论在哪一个层面加独占锁,都会致使真个数据库单线程化)加独占锁(即便是对于WiredTiger存储引擎也是),在这期间不能进行其余任何的读写操做。因此若是$isolated的操做执行的时间过长,会大大的影响系统的并发性能。

例子:

db.foo.update( 
 { status : "A" , $isolated : 1 }, 
 { $inc : { count : 1 } }, 
 { multi: true } 
) 

注:上面说的影响不是说能够保证多个文档更新的原子性,$ isolation隔离操做符不为写入操做提供"all-or-nothing"原子性(原子性的定义是要么所有成功,要么所有失败,$isolation不能保证出错回滚)。没有$isolation运算符,多更新将容许其余操做与此更新交错。 若是这些交错操做包含写入,则更新操做可能会产生意外的结果。 经过指定$ isolated,您能够保证整个多重更新的隔离。

总结以下:

  • $ isolation不保证多个文档操做的原子性。
  • $ isolation保证多个文档操做不会被跟其余操做交错。
  • $ isolation保证此操做在进行到某一个文档的更新的时候,在不提交或者回滚以前,不会被客户端看到。也就是说不会致使这个文档的查询产生脏读。(这一段是个人理解 不必定对)

   

$isolated使用的场景很苛刻。

因为单个文档能够包含多个嵌入文档,单个文档的原子性对于许多实际使用状况是足够的。 对于一系列写入操做必须在单个事务中操做的状况,您能够在应用程序中实现两阶段提交。

可是,两阶段的提交只能提供相似事务的语义。 使用两阶段提交确保数据一致性,可是在两阶段提交或回滚期间,应用程序能够返回中间数据。

 

 

副本集中使用readConcern:

 

在使用副本集的时候,写入操做只写入到master节点,slaver节点从master节点同步数据,因此读操做可能读取到没有同步到其余slaver的数据。

readConcern:读隔离(

readConcern选项可用于如下操做:

用于副本集和副本集分片的readConcern查询选项肯定从查询返回哪些数据。

readConcern级别:

"local":默认。该查询返回实例的最新数据。不保证数据已写入大多数副本集成员(便可以回滚)。

"majority":该查询会将实例的最新数据确认为已写入副本集中的大多数成员。要使用majority级别,您必须使用--enableMajorityReadConcern命令行选项启动mongod实例(若是使用配置文件,则将replication.enableMajorityReadConcern设置为true)。

"linearizable"(add in version3.4):该查询返回反映全部成功写入的数据。

   

这么说若是配置了linearizable 那么针对一个集合的查询就能够避免脏读了。由于Mongdb没有事务,因此也就不存在幻读和不可重复读的定义了。不过这个功能是在当前最新的3.4版本才有的。

 

readConcern 解决什么问题?

的初衷在于解决『脏读』的问题,好比用户从 MongoDB primary 上读取了某一条数据,但这条数据并无同步到大多数节点,而后 primary 就故障了,从新恢复后 这个primary 节点会将未同步到大多数节点的数据回滚掉,致使用户读到了『脏数据』。

当指定 readConcern 级别为 majority 时,能保证用户读到的数据『已经写入到大多数节点』,而这样的数据确定不会发生回滚,避免了脏读的问题()readConcern 能保证读到的数据『不会发生回滚』,但并不能保证读到的数据是最新的,这个官网上也有说明:

在使用副本集的时候,不管读取关注级别如何,节点上的最新数据可能不会反映系统中最新版本的数据。

有用户误觉得,指定为 majority 时,客户端会从大多数的节点读取数据,而后返回最新的数据。

实际上并非这样,不管何种级别的 注意事项

  1. 检索事务开始:

var t = db.transactions.findOne( { state: "initial" , source: "A", destination: "B"} )

  1. Update transaction state to pending:

 

db.transactions.update(

{ _id: t._id, state: "initial" },

{

$set: { state: "pending" },

$currentDate: { lastModified: true }

}

)

WriteResult nnModified1

nMatchednModified0

  1. Apply the transaction to both accounts.

 

db.accounts.update(

{ _id: t.source, pendingTransactions: { $ne: t._id } },

{ $inc: { balance: -t.value }, $push: { pendingTransactions: t._id } }

)

db.accounts.update(

{ _id: t.destination, pendingTransactions: { $ne: t._id } },

{ $inc: { balance: t.value }, $push: { pendingTransactions: t._id } }

)

  1. Update transaction state to applied

db.transactions.update(

{ _id: t._id, state: "pending" },

{

$set: { state: "applied" },

$currentDate: { lastModified: true }

}

)

  1. remove both accounts' list of pending transactions

 

db.accounts.update(

{ _id: t.source, pendingTransactions: t._id },

{ $pull: { pendingTransactions: t._id } }

)

db.accounts.update(

{ _id: t.destination, pendingTransactions: t._id },

{ $pull: { pendingTransactions: t._id } }

)

  1. Update transaction state to done.

 

db.transactions.update(

{ _id: t._id, state: "applied" },

{

$set: { state: "done" },

$currentDate: { lastModified: true }

}

)

相关文章
相关标签/搜索