前言
此篇博文是 Mongdb 基础系列之一;
本文为作者的原创作品,转载需注明出处;
简介
在集群环境中,非常容易读到旧的数据或者是还没有持久化的数据,其实这类数据,我们统称为脏数据
;自从 MongoDB 3.4 以来,MongoDB 通过增加了新的特性 linearizable read concern (线性读取的方式)来获取最新的持久化的数据;不过要注意的是
,该方式只对单个文档的读取有效;本文将会介绍,MongoDB 是如何通过 db.collection.findAndModify() 来读取最新的数据,并且该数据不能被回滚;
原理
db.collection.findAndModify() 是如何保证对单个文档的读取不会读取到“脏数据”的呢?其实原理并不复杂,就是利用了 MongoDB 对单个文档写操作的事务原子性,当读取的时候,通过对该 document 的一个 dummy 字段进行 udpate,既使用了 document 上的排他锁,这样的话,在读的过程当中就不会有其它的进程对其进行修改该了;具体操作如下:
- db.collection.findAndModify() 必须使用准确的完整匹配,而且被查询的字段上必须有 Unique index;
- findAndModify() 必须对该文档进行修改;通常是对一个 dummy 字段进行修改;
- findAndModify() 必须使用 write concern { w: “majority” };这一步很显然,必须保证写入成功,这里的写入成功是指,上一步的 udpate 操作必须同步到多个从节点,为什么要必须同步到其它多个从节点呢?因为不但要对 primary 节点上排它锁,而且必须对其相关的子节点上排它锁;这样,当前被读取的数据就不存在“脏读”的情况了;
例子
我们来看下面这个例子,
1 | db.products.insert( [ |
显然,这个例子中,我们使用了一个 dummy 字段_dummy_field
来供 db.collection.findAndModify() 读取的时候执行写操作;但是,如果在执行 db.collection.findAndModify() 操作之前,该 document 并没有该字段_dummy_field
,那么 findAndModify() 会自动为其创建一个;
下面,来看一下,我们是如何线性读取 sku 的;
① 创建 unique index
首先,需要对被读取的字段 sku 建立索引;
1 | db.products.createIndex( { sku: 1 }, { unique: true } ) |
② 使用 findAndModify 读取 committed data
1 | var updatedDocument = db.products.findAndModify( |
- 精确匹配,可以看到,对要查询的字段 sku 必须使用精确匹配
- 必须执行 update 操作,这里默认是对
_dummy_field
进行修改;保证读取的时候,对该字段上了排它锁,也就避免了脏读
的情况; - new: true,可选字段,如果设置为 true,返回被修改后的 document,而不是先前的 document;(这里的 modified 其实可以忽略,因为毕竟只是对一个 dummy 字段进行了 udpate)
writeConcern: { w: "majority", wtimeout: 5000 }
,正如前文分析的那样,为了保证不会产生脏读
的情况,必须使用majority
;而wtimeout
呢?为什么需要设置一个超时呢?显然呀,如果多个 processes 并发的在使用线性读的方式呢.. 是吧,都会在竞争该文档的锁,那么为了避免长时间的锁竞争,超时设置是必须的;
References
https://docs.mongodb.com/manual/tutorial/perform-findAndModify-linearizable-reads/