MongoDB 基础系列十五:增删查改 CRUD 之 Query - Read Isolation( Read Concern )

前言

此篇博文是 Mongdb 基础系列之一;

本文为作者的原创作品,转载需注明出处;

简介

一个查询中的 readConcern 选项决定了在集群环境中或者分片集群环境中如何返回数据;

Read Concern 并不神秘,其实也就是为了解决这样的一种情况,在集群环境中,势必存在主、从 instances,那么势必存在主从之间的同步的关系,那么问题来了,如果我们需要对刚写入集群的数据进行迅速的读取呢?要知道刚写入的数据是需要在集群中进行复制的,如果当集群达到一定的规模后,这个复制就是比较的耗时的,那么正当写入的数据在多个 instances 之间同步的时候,需要对刚写入数据的进行读取操作,那么这个时候,我们该如何快速的响应?所以,MongoDB 提供了三种处理方式,“local”“majority” 以及 “linearizable”,”local” 既是说,只要在本地完成写入,那么就从本地读取;”majority”即是说,当大多数副本实例写入成功以后,再从主节点获取数据;”linearizable”与”majority”的原理差不多,不过修复了一些“majority”的 bug,并且支持 MMAPv1 引擎;下面,就来更为详细的了解这三种方式,

Read Concern Levels

Changed in version 3.4: Adds support for “linearizable” read concern.

MongoDB 提供了如下的集中 read convern levels,

  • local
    默认设置,Default,只要本地 instance 写入成功,便立即从本地的 instance 返回,并不会考虑该写入数据是否在集群中的多个实例中是否已经同步;

    笔者补充,随着主从复制的技术的发展,集群中各个实例之间的赋值已经不再是保证数据一定是写入数据库中才算同步成功了,而是只要保证写入操作写入实例的操作日志中,既算是写入成功了;这样可以最大限度的提升主从复制的性能;

    local 的读取方式显然是有隐患的,如果该写入操作还没有确定同步到了其它 instances 中,就立刻返回了,并且不巧,就在那一瞬间,当前的 instance 也挂掉了,那么就导致了,该插入并返回给用户的数据永久丢失掉了;所以,一般在集群或者是分片集群的环境下,要保证大多数从 instances 写入成功以后,再把写入结果返回给用户,才是正确的选择,这也是下面 majoritylinearizable 所涉及的内容;

  • majority

    当被查询的数据已经确认写入大多数集群中的 instances 以后,另外一个客户端才可以对该数据进行读取;

    使用 majority 需要注意的有,

    可以看到,使用 majority 的方式会有诸多的限制,必须使用 WiredTiger 存储引擎,而且必须将选举协议设置为 1;

  • linearizable

    majority 类似,也是等写入操作在大多数的的集群节点中写入以后,才允许其它客户端进行读取;writeConcernMajorityJournalDefault 设置为 true,则等待大多数写入成功,否则不等待;

    从后面的 Real Time Order 小节中我们可以看到,linearizable 还有一层意思就是,对同一个文档的并发的读、写操作是线性执行的;

Storage Engine and Drivers Support

同样的,可以通过 serverStatus 命令通过查询 storageEngine.supportsCommittedReads 字段值来表明当前的存储引擎是否支持 “majority”

1
2
3
4
5
6
7
> db.serverStatus().storageEngine
{
"name" : "wiredTiger",
"supportsCommittedReads" : true,
"readOnly" : false,
"persistent" : true
}

"supportsCommittedReads" : true 表示支持 “majority”

readConcern 选项设置

在一系列的读取操作中设置 read concern level 即可,设置的格式为,

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

readConcern 可以作用到如下的操作中:

  • find command
  • aggregate command and the db.collection.aggregate() method
  • distinct command
  • count command
  • parallelCollectionScan command
  • geoNear command
  • geoSearch command

注意事项

读取自己写入的数据

如果在读操作中使用 “majority” 或者是 “linearizable” 的时候,在主节点上上的写操作中使用 write concern { w: “majority” },允许在单线程中写操作可以读取它自己的写入数据;

这种情况非常的常见,比如我有这么一个事务性操作,需要先写入,然后再读取,然后再写入;

Real Time Order

Combined with “majority” write concern, “linearizable” read concern enables multiple threads to perform reads and writes on a single document as if a single thread performed these operations in real time; that is, the corresponding schedule for these reads and writes is considered linearizable.

这里需要注意最后一句话,the corresponding schedule for these reads and writes is considered linearizable. 也就是说,当使用 linearizable 的时候,多个线程可以同时在同一个 document 上执行读、写操作,但是这些读、写操作是线性执行的,也就是说,是一个接一个按照先后顺序执行的;

性能对比

linearizablemajoritylocal 都要慢许多;

Always use maxTimeMS with linearizable read concern in case a majority of data bearing members are unavailable. maxTimeMS ensures that the operation does not block indefinitely and instead ensures that the operation returns an error if the read concern cannot be fulfilled.

当使用 linearizable read concern 进行读取操作的时候记得经常使用 maxTimeMS,以防在某些子节点不可用的时候依然可以工作;maxTimeMS 可以保证一个读操作不会无限制的被阻塞,超时以后即返回;

1
2
3
4
5
6
7
8
db.restaurants.find( { _id: 5 } ).readConcern("linearizable").maxTimeMS(10000)

db.runCommand( {
find: "restaurants",
filter: { _id: 5 },
readConcern: { level: "linearizable" },
maxTimeMS: 10000
} )

References

https://docs.mongodb.com/manual/reference/read-concern/

https://stackoverflow.com/questions/42615319/the-difference-between-majority-and-linearizable