一、什么是 readConcern?¶
1、定义:
readPreference 决定从哪个节点读取,readConcern 决定该节点的哪些数据是可读的。主要保证事务中的隔离性,避免脏读。类似于关系数据库的隔离级别。可选值包括:
- 目前 readConcern 主要用于跟 mongos 与 config server 的交互上 //使用 readConcern 需要配置 replication.enableMajorityReadConcern 选项
- 只有支持 readCommited 隔离级别的存储引擎才能支持 readConcern,比如 wiredtiger 引擎,而 mmapv1 引擎则不能支持。
2、如何配置:
- 支持客户端灵活配置读取策略(readConcern) //以满足不同场景的需求。
- replica set or sharded cluster 全局设置 //从 MongoDB 4.4 开始,副本集和分片集群支持设置全局默认的 readConcern。没有显式指定 readConcern 的操作将会继承全局默认设置。使用 setDefaultRWConcern
3、可选值
MongoDB 支持的 ReadConncern 选项如下:{ level: <value>}
value 的可选值:
- available:读取所有可用的数据;返回从实例查询到的数据,但不能保证数据已经被写到了大多数副本集成员(即可能回滚)。默认从 primary 读
- local:读取所有可用且属于当前分片的数据;//返回从实例查询到的数据,但不能保证数据已经被写到了大多数副本集成员(即可能回滚)。默认从 primary 和 secondary 读
- majority:读取在大多数节点上提交完成的数据;//要想使用 majority 这个模式的 readConcern,MongoDB 必须使用 wireTiger 存储引擎。
- snapshot:读取最近快照中的数据;//快照是 mongod 实例中数据在特定时间点的完整副本。可以为整个集群或副本集检索快照元数据,或者为集群中的单个 configserver 检索快照元数据。
- linearizable:可线性化读取文档;//该查询返回的数据反映了在读操作开始之前完成的所有多数人认可的成功写操作。在返回结果之前,查询可能会等待并发执行的写操作传播到大多数副本集成员。
二、readConcern: local 和 available¶
在复制集中 local 和 available 是没有区别的。两者的区别主要体现在分片集群上。
考虑以下场景:
- 一个 chunk x 正在从 shard1 向 shard2 迁移;//保证集群中数据的均衡
- 整个迁移过程中 chunk x 中的部分数据会在 shard1 和 shard2 中同时存在,但源分片 shard1 仍然是 chunk x 的负责方:
- -> 所有对 chunk x 的读写操作仍然进入 shard1;
- -> config 中记录的信息 chunk x 仍然属于 shard1;
- 此时如果读 shard2,则会体现出 local 和 available 的区别:
- -> local:只取应该由 shard2 负责的数据(不包括 x);
- -> available:shard2 上有什么就读什么(包括 x);
注意事项:
- 虽然看上去总是应该选择 local,但毕竟对结果集进行过滤会造成额外消耗。在一些无关紧要的场景(例如统计)下,也可以考虑 available;
- MongoDB <=3.6 不支持对从节点使用 {readConcern: "local"};
- 从主节点读取数据时默认 readConcern 是 local,从从节点读取数据时默认 readConcern 是 available(向前兼容原因)。

三、readConcern: majority¶
只读取大多数据节点上都提交了的数据。考虑如下场景:
- 集合中原有文档 {x: 0};
- 将 x 值更新为 1;

如果在各节点上应用 {readConcern: "majority"} 来读取数据:
| 时间 | P | S1 | S2 |
|---|---|---|---|
| t0 | x=0 | x=0 | x=0 |
| t1 | x=0 | x=0 | x=0 |
| t2 | x=0 | x=0 | x=0 |
| t3 | x=1 | x=0 | x=0 |
| t4 | x=1 | x=0 | x=0 |
| t5 | x=1 | x=1 | x=0 |
| t6 | x=1 | x=1 | x=1 |
readConcern: majority 的实现方式
t3 时刻的 Secondary1,此时:
- 对于要求 majority 的读操作,它将返回 x=0;
- 对于不要求 majority 的读操作,它将返回 x=1;
如何实现?
节点上维护多个 x 版本,MVCC 机制,MongoDB 通过维护多个快照来链接不同的版本:
- 每个被大多数节点确认过的版本都将是一个快照;
- 快照持续到没有人使用为止才被删除;

四、实验&验证: readConcern : ”majority” vs “local”¶
1、实验:
安装 3 节点复制集。
- 注意配置文件内 server 参数 enableMajorityReadConcern
- 将复制集中的两个从节点使用 db.fsyncLock() 锁住写入(模拟同步延迟)
配置文件中的配置信息:
replication:
oplogSizeMB: 10
replSetName: repl
enableMajorityReadConcern: true
//从 MongoDB 5.0 开始,enableMajorityReadConcern 和 ——enableMajorityReadConcern 不能更改,由于存储引擎的改进,它们总是被设置为 true。
2、验证:
(1) 从节点 db.fsyncLock() 锁住写入
repl:SECONDARY> db.fsyncLock()
(2) 主节点模拟写入数据
repl:PRIMARY> db.test_read.insert({"name":1})
(3) 从节点进行数据的读取
repl:PRIMARY> db.test_read.find().readConcern("local")
{ "_id" : ObjectId("635bb82564d7be2f932c3a0a"), "name" : 1 }
repl:PRIMARY> db.test_read.find().readConcern("majority") //等待,直到从节点执行了 db.fsyncUnlock(),会立马返回,但是无结果
#再次查询,此时从节点已经写入,可以查到数据
repl:PRIMARY> db.test_read.find().readConcern("majority")
{ "_id" : ObjectId("635bb82564d7be2f932c3a0a"), "name" : 1 }
3、结论:
- 使用 local 参数,则可以直接查询到写入数据
- 使用 majority,只能查询到已经被多数节点确认过的数据
- update 与 remove 与上同理。
五、readConcern: majority 与脏读¶
MongoDB 中的回滚:
- 写操作到达大多数节点之前都是不安全的,一旦主节点崩溃,而从节还没复制到该次操作,刚才的写操作就丢失了;
- 把一次写操作视为一个事务,从事务的角度,可以认为事务被回滚了。所以从分布式系统的角度来看,事务的提交被提升到了分布式集群的多个节点级别的“提交”,而不再是单个节点上的“提交”。在可能发生回滚的前提下考虑脏读问题
- 如果在一次写操作到达大多数节点前读取了这个写操作,然后因为系统故障该操作回滚了,则发生了脏读问题;使用 {readConcern: "majority"} 可以有效避免脏读
六、readConcern: 如何实现安全的读写分离¶
考虑如下场景:
向主节点写入一条数据;立即从从节点读取这条数据。如何保证自己能够读到刚刚写入的数据?
下述方式有可能读不到刚写入的订单:
db.orders.insert({ oid: 101, sku: ”kite", q: 1})
db.orders.find({oid:101}).readPref("secondary")
使用 writeConcern + readConcern majority 来解决:
db.orders.insert({ oid: 101, sku: "kiteboar", q: 1}, {writeConcern:{w: "majority"}})
db.orders.find({oid:101}).readPref(“secondary”).readConcern("majority")

七、读隔离性和 MySQL的对比¶
readConcern 主要关注读的隔离性,ACID 中的 Isolation,但是是分布式数据库里特有的概念。readCocnern:majority 对应于 MysQL 事务中隔离级别中的哪一级?
Read Uncommited
--> Read Committed
Repeatable Read
Seriazable
//所以说使用 readConcern:majority 就可以达到 Read Committed 隔离级别的效果。