添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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

当进行读操作的时候会加读锁,这个时候其他读操作可以也获得读锁。但是不能或者写锁。

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

所以按照这个道理,是不会出现同时修改同一个文档(如执行++操作)导致数据出错的情况。

而且按照这个道理,因为写操作会阻塞读操作,所以是不会出现脏读的。

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

读写锁的粒度:

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

findAndModify

findAndModify可以保证修改+返回结果(修改前或者修改后都可以)这两个步骤的原子性。

修改并返回单个文档。 默认情况下,返回的文档不包括对更新所做的修改。

db.collection.findAndModify({

query: <document>,

sort: <document>,

remove: <boolean>,

update: <document>,

new: <boolean>,

fields: <document>,

upsert: <boolean>,

bypassDocumentValidation: <boolean>,

writeConcern: <document>,

collation: <document>

query

document 可选的。 使用这个查询来定位需要修改的记录。 虽然查询可能匹配多个文档,但findAndModify()只会选择一个要修改的文档。

sort:

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

remove:

boolean 。标识删除操作。update和remove必须选其一。

update

document 更新操作。 update和remove必须选其一。

new

boolean。可选的。 当为true时,返回修改后的文档而不是原始文件。删除的时候,设置为true没有意义。

fields

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

upsert

boolean。适用于update,当没有query匹配的时候,是否插入。

writeConcern:

参考writeConcern的说明。

update和findAndModify

默认情况下,update()方法更新单个文档。 设置多参数以更新与查询条件匹配的所有文档。

update可以更新多个文档,但是Mongodb只保证单个文档的写入是原子性的。

update()方法返回一个包含操作状态的WriteResult对象。要返回更新的文档,请使用find()方法。但是,其他更新可能已经在更新和文档检索之间修改了文档。此外,如果更新仅修改了单个文档,但是多个文档匹配,则需要使用其他逻辑来标识更新的文档。

findAndModify可能引起的原子性问题:

当findAndModify()包含upsert:true选项,并且查询字段不是唯一索引时,该方法可能会在某些情况下多次插入文档。

db.people.findAndModify({

query: { name: "Andy" },

sort: { rating: 1 },

update: { $inc: { score: 1 } },

upsert: true

当多个客户端同时发出了这个指令,然后在服务端并行执行,而都没有找到query的匹配,可能同时执行了多个upsert操作。导致数据重复。

如果不使用upsert,就没有这种问题。

findAndModify在分片集群中的要求:

在分片环境中使用findAndModify时,查询必须包含分片键。

findAndModify示例:

实例说明了在一个相同的文档中如何确保嵌入字段关联原子操作( update :更新)的字段是同步的。

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 :

    { $inc : { count :
									1 } },

    { multi:
							true }

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

总结如下: