读写锁

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 :
1 },

    { $inc : { count :
1 } },

    { multi:
true }

)

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

总结如下:

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

 
 

$isolated使用的场景很苛刻。

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

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

 

 

副本集中使用readConcern:

 

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

readConcern:读隔离(New in version 3.2.):

readConcern: { level: <"majority"|"local"|"linearizable"> }

readConcern选项可用于以下操作:

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

readConcern级别:

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

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

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

 
 

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

 

readConcern 解决什么问题?

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

当指定 readConcern 级别为 majority 时,能保证用户读到的数据『已经写入到大多数节点』,而这样的数据肯定不会发生回滚,避免了脏读的问题(这段话不是来自官方文档,是阿里的一篇文章,这句话我不太认可,即使写入了大部分的节点,也不代表不会回滚,因为可能选举出来新节点正好没有同步这部分数据)。

需要注意的是,readConcern 能保证读到的数据『不会发生回滚』,但并不能保证读到的数据是最新的,这个官网上也有说明:

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

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

实际上并不是这样,无论何种级别的 readConcern,客户端都只会从『某一个确定的节点』(具体是哪个节点由 readPreference 决定)读取数据,该节点根据自己看到的同步状态视图,只会返回已经同步到大多数节点的数据。

 

注意事项

  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()对象。
成功更新后,n次和nModified显示1。

在更新语句中,状态:"初始"条件确保没有其他进程已更新此记录。
如果nMatched和nModified为0,则返回到第一步以获取其他事务并重新启动该过程。

  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 }

}

)

Mongodb的锁 原子性 隔离性 一致性的更多相关文章

  1. Mongodb中的 原子性 隔离性

    读写锁 Mongodb使用读写锁来来控制并发操作: 当进行读操作的时候会加读锁,这个时候其他读操作可以也获得读锁.但是不能或者写锁. 当进行写操作的时候会加写锁,这个时候不能进行其他的读操作和写操作. ...

  2. ACID隔离性

    数据库ACID 一致性 原子性  隔离性  持久性 隔离性: 1.读未提交 2.读已提交 3.可重复读 4.串行 读未提交:容易引起脏读 读已提交:容易引起幻读(前后读到的行数不一致) 场景: A事务 ...

  3. 事务四大特征:原子性,一致性,隔离性和持久性(ACID)

    一.事务 定义:所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位. 准备工作:为了说明事务的ACID原理,我们使用银行账户及资金管理的案例进行分析. [sql] ...

  4. ACID 原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)。

    https://en.wikipedia.org/wiki/ACID https://zh.wikipedia.org/wiki/ACID //ACID compliant , row-level l ...

  5. 事务的四大属性ACID即事务的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability.。

    事务的四大属性ACID即事务的原子性(Atomicity).一致性(Consistency).隔离性(Isolation).持久性(Durability.. 原子性(Atomicity) 原子性是指事 ...

  6. 数据库事务ACID特性(原子性、一致性、隔离性、持久性)

    ACID特性: 原子性(Atomicity).一致性(Consistency).隔离性(Isolation).持久性(Durability) 原子性:一个事务必须被视为一个不可分割的最小工作单元,整个 ...

  7. acid-事务的原子性、一致性、隔离性、持久性

    博客分类: oracle-dba   原子性  多个事情组成一个单元,要么同时成功或失败,不能只运行其中一个 一致性  事务处理要将数据库从一种状态转变为另一种状态. 一旦提交了修改数据,那么其它人读 ...

  8. mysql 事务 隔离性 锁

    1.四大特性 1.1 原子性(Atomicity) 一个事务是不可分割的最小工作单位.一个事务是不可分割的最小工作单位. 利用undo log保证原子性,undo log记录的是操作的反向语句,例如执 ...

  9. Actor模型浅析 一致性和隔离性

    一.Actor模型介绍 在单核 CPU 发展已经达到一个瓶颈的今天,要增加硬件的速度更多的是增加 CPU 核的数目.而针对这种情况,要使我们的程序运行效率提高,那么也应该从并发方面入手.传统的多线程方 ...

随机推荐

  1. Computed Styles

    The style object offers no information about the styles that have cascaded from style sheets and aff ...

  2. java游戏服务器 建造者模式

    这里的具体的建造者ConcreteBuilder可以多个 这里我们以建造汽车为例: 假设汽车需要:方向盘.汽车壳.还有四个轮子  ---------这三样才能跑起来 如果你因为粗心漏了其中的某个建造过 ...

  3. Java并发编程之程序运行堆栈分析

    Java程序运行的堆栈分析 1.JVM运行时数据区 JVM通过加载class文件的数据来执行程序.JVM在运行时会划分不同的区域以存放数据.如下图所示: 线程共享部分:所有线程都能访问这块内存的数据, ...

  4. Python爬虫学习==>第九章:正则表达式基础

    学习目的: 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特点字符.及这些特点字符组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑. 正式步骤 Step1 ...

  5. PJzhang:一道看线索找答案的逻辑题

    猫宁!!! 这道逻辑题,2年前就有打算解决,但是没上心,今天抽空梳理出来了思路,逻辑上可以跑的通,不至于以后慢慢忘了,这道题和数独题基本类似,但是也许更花时间,做这种题最好看着线索列图标,省的不停翻页 ...

  6. 【AMAD】python-goose -- HTML Content/Article 提取器

    动机 简介 用法 个人评分 动机 新闻网页,结构大多是类似的. 所以,能不能用一种通用的爬取方法来提取其中的数据? 简介 Goose最初是一个Java项目,在2011年被转为了scala项目1. Py ...

  7. DNS简单搭建

    一.安装服务 设置主机名 [root@localhost named]# hostnamectl set-hostname k1.kk.cn [root@localhost named]# bash ...

  8. 关于Typescript - HTMLElement上使用append / prepend函数的问题

    因最近在做浏览器打印界面水印的问题,用到后台动态创建标签,样式的处理用到了append,prend函数,Angular build打包的时候却抛出了异常↓ ERROR in src/app/route ...

  9. Angular中引入外部js的使用方式

    在Angular中我们或许会用到部分外部插件的时候,像Bootstrap,Jquery这些当然我们可以通过Npm安装包的形式引入,但是还有一些其它的js库需要引入的话,我们又应该怎样操作呢? 在这里做 ...

  10. python 爬虫 基于requests模块的get请求

    需求:爬取搜狗首页的页面数据 import requests # 1.指定url url = 'https://www.sogou.com/' # 2.发起get请求:get方法会返回请求成功的响应对 ...