因果一致性和读写问题
使用 MongoDB 的因果一致的 Client 会话,读写问题的不同组合提供了不同的因果一致性保证。如果定义因果一致性以表示耐久性,则下表列出了各种组合提供的特定保证:
Read Concern | Write Concern | 阅读自己的文章 | Monotonic reads | Monotonic writes | 写跟读 |
---|---|---|---|---|---|
"majority" | "majority" | ✅ | ✅ | ✅ | ✅ |
"majority" | { w: 1 } | ✅ | ✅ | ||
"local" | { w: 1 } | ||||
"local" | "majority" | ✅ |
如果因果一致性表示持久性,那么从表中可以看出,只有具有"majority"读关注点的读取操作和具有"majority"写入关注点的写入操作才能保证所有四个因果一致性保证。也就是说,因果一致的 Client 会话仅能保证以下情况的因果一致性:
-
具有"majority"阅读关注的阅读操作;即读取操作,该操作返回大多数副本集成员已确认且持久的数据。
-
"majority"写关注的写操作;即要求确认该操作已应用于大多数副本集投票成员的写入操作。
如果因果一致性并不意味着持久性(即,写操作可能会回滚),则具有{ w: 1 }写入关注点的写操作也可以提供因果一致性。
Note
在某些情况下(但不一定在所有情况下),读和写关注点的其他组合也可以满足所有四个因果一致性保证。
读关注点"majority"和写关注点"majority"确保四个因果一致性保证即使在情况(例如带有网络分区的情况)中也是如此,因为副本集中的两个成员暂时地认为它们是主要的。尽管两个主数据库都可以完成{ w: 1 }写关注,但只有一个主数据库能够完成"majority"写关注。
例如,考虑网络分区划分五个成员副本集的情况:
With the above partition
-
具有"majority"写关注的写操作可以在
P
新完成,但是不能在P
旧完成。 -
具有{ w: 1 }写关注的写可以在
P
旧或P
新上完成。但是,一旦P
old 的写入(以及复制到S
1 的写入)回滚,则这些成员恢复与副本集其余部分的通信。 -
成功对
P
new 进行"majority"写关注后,与"majority"读关注因果一致的读操作可以观察到P
new,S
2 和S
3 上的写操作。一旦读入,读操作也可以观察到P
old 和S
1 上的写操作。与副本集的其余部分进行通信,并与副本集的其他成员进行同步。在分区期间对P
old 和/或复制到S
1 的所有写操作都会回滚。
Scenarios
为了说明读写关注点要求,在以下情况下,Client 端向 Client 端发出了一系列操作,并对副本集进行了读写关注点的各种组合:
阅读关注“多数”并写关注“多数”
在因果一致会话中使用读关注点"majority"和写关注点"majority"提供了以下因果一致性保证:
✅自己读✅单调读 read 单调写✅写跟随读
方案 1(读取关注点“多数”和写入关注点“多数”)
在具有两个主键的过渡期间,由于只有P
new 可以满足{ w: "majority" }写问题,因此 Client 端会话可以成功发出以下操作序列:
Sequence | Example |
---|---|
1.将关注点"majority"写为 1 到P 新 | |
2.读为 1,读意为"majority"至 S 23.写 2,写关注点"majority"至 P 新4.以关注点"majority"到 S 3 读取 2 | 对于项目A ,将qty 更新为50 。阅读项目 A 。对于 qty 小于或等于50 的项目,将 restock 更新为true 。阅读项目 A 。 |
✅ 读自己写的东西 | 读取 1 从S 2 读取反映写入 1 之后状态的数据。 |
读取 2 从S
1 读取数据,该数据反映了 Write1 1 之后是 Write 2 之后的状态。
|✅ 单音符号读取 | Read2 从S
3 读取反映 Read1 之后状态的数据。
|✅ 单调写入 | Write2 更新P
new 上的数据,以反映 Write1 之后的状态。
|✅ Writes 跟着读取 | Write2 更新P
new 上的数据,该数据反映了 Read1 之后的数据状态(即,较早的状态反映了 Read1 读取的数据)。
方案 2(读取关注点“多数”和写入关注点“多数”)
考虑一个替代序列,其中具有读关注"majority"的 Read1 路由到S
1:
Sequence | Example |
---|---|
1.将关注点"majority"写为 1 到P 新 | |
2.阅读关注度为"majority"至 S 1 的 13.写 2,写关注点"majority"至 P 新4.以关注点"majority"到 S 3 读取 2 | 对于项目A ,将qty 更新为50 。阅读项目 A 。对于 qty 小于或等于50 的项目,将 restock 更新为true 。阅读项目 A 。 |
按照这种 Sequences,直到多数提交点在P
old 上前进,Read1 才返回。直到P
old 和S
1 可以与副本集的其余部分通信时,才会发生这种情况。那时P
old 已下台(如果尚未退出),并且两个成员与副本集的其他成员同步(包括 Write1)。
✅ 读自己写的东西 | 读 1 反映了写 1 1 之后的数据状态,尽管在网络分区已修复并且该成员已与副本集的其他成员同步之后。 |
读取 2 从S
3 读取数据,该数据反映了 Write1 1 之后是 Write 2 之后的状态。
|✅ 单声道读取 | Read2 从S
3 读取反映 Read1 之后状态的数据(即,较早的状态反映在 Read1 读取的数据中)。
|✅ 单调写入 | Write2 更新P
new 上的数据,以反映 Write1 之后的状态。
|✅ Writes 跟着读取 | Write2 更新P
new 上的数据,该数据反映了 Read1 之后的数据状态(即,较早的状态反映了 Read1 读取的数据)。
阅读关注“多数”并发表关注\ {w: 1}
在因果一致性会话中使用读关注点"majority"和写关注点{ w: 1 }提供以下因果一致性保证如果因果一致性暗示持久性:
❌自己读✅单调读 read 单调写✅写跟随读
如果因果一致性并不意味着耐用性:
✅自己读✅单调读 read 单调写✅写跟随读
方案 3(“关注多数”和“关注{w: 1}
”)
在具有两个主键的过渡期内,由于P
old 和P
new 都可以满足{ w: 1 }写问题,因此 Client 端会话可以成功发出以下操作序列,但不能因果一致 如果因果一致性暗示持久性 :
Sequence | Example |
---|---|
1.写入关注度为{ w: 1 }至P 老的 1 | |
2.读为 1,读意为"majority"至 S 23.写 2,写关注点{ w: 1 }至 P 新4.以关注点"majority"到 S 3 读取 2 | 对于项目A ,将qty 更新为50 。阅读项目 A 。对于 qty 小于或等于50 的项目,将 restock 更新为true 。阅读项目 A 。 |
按照这个 Sequences
-
直到多数提交点在 Write1 的时间之后
P
new 前进之前,Read1 才返回。 -
直到多数提交点在 Write2 之前
P
new 前进之前,Read2 才能返回。 -
网络分区修复后,Write1 将回滚。
➤如果因果一致性意味着持久性
❌ 读自己写的东西 | Read1 从S 2 读取未反映 Write1 之后状态的数据。 |
✅ 单调发音 | Read2 从S 3 读取数据,该数据反映了 Read1 之后的状态(即,较早的状态反映在 Read1 读取的数据中)。 |
❌ 单调写作 | Write2 更新P new 上的数据,这些数据不反映 Write1 之后的状态。 |
✅ 读后随笔写 | Write2 更新P new 上的数据,该数据反映了 Read1 之后的状态(即,较早的状态反映了 Read1 读取的数据)。 |
➤如果因果一致性并不意味着耐用性
✅ 读自己写的东西 | Read1 从S 读取数据。2 返回反映与 Write1 等效的状态的数据,然后回退 Write1. |
✅ 单调发音 | Read2 从S 3 读取数据,该数据反映了 Read1 之后的状态(即,较早的状态反映在 Read1 读取的数据中)。 |
✅ 单调写作 | Write2 更新P new 上的数据,这等效于 Write1 之后回退 Write1 之后的数据。 |
✅ 读后随笔写 | Write2 更新了P new 上的数据,该数据反映了 Read1 之后的状态(即,其较早的状态反映了 Read1 读取的数据)。 |
方案 4(读取关注“多数”并关注{w: 1}
)
考虑一个替代序列,其中具有读关注"majority"的 Read1 路由到S
1:
Sequence | Example |
---|---|
1.写入关注度为{ w: 1 }至P 老的 1 | |
2.阅读关注度为"majority"至 S 1 的 13.写 2,写关注点{ w: 1 }至 P 新4.以关注点"majority"到 S 3 读取 2 | 对于项目A ,将qty 更新为50 。阅读项目 A 。对于 qty 小于或等于50 的项目,将 restock 更新为true 。阅读项目 A 。 |
按此 Sequences:
- 在多数提交点在
S
1 上前进之前,Read1 将不会返回。直到P
old 和S
1 可以与副本集的其余部分通信时,才会发生 Read1.那时,P
old 已降级(如果尚未降级),Write1 从P
old 和S
1 回滚,并且两个成员与副本集的其他成员同步。
➤如果因果一致性意味着持久性
❌ 读自己写的东西 | Read1 读取的数据不能反映已回退的 Write1 的结果。 |
✅ 单调发音 | Read2 从S 3 读取数据,该数据反映了 Read1 之后的状态(即,其较早的状态反映了 Read1 读取的数据)。 |
❌ 单调写作 | Write2 更新P new 上的数据,该数据不反映 Write1 之后的状态,该状态早于 Write2 但已回滚。 |
✅ 读后随笔写 | Write2 更新了P new 上的数据,该数据反映了 Read1 之后的状态(即,其较早的状态反映了 Read1 读取的数据)。 |
➤如果因果一致性并不意味着耐用性
✅ 读自己写的东西 | Read1 返回的数据反映了 Write1 的最终结果,因为 Write1 最终会回滚。 |
✅ 单调发音 | Read2 从S 3 读取数据,该数据反映了 Read1 之后的状态(即,较早的状态反映了 Read1 读取的数据)。 |
✅ 单调写作 | Write2 更新P new 上的数据,这等效于 Write1 之后回退 Write1 之后的数据。 |
✅ 读后随笔写 | Write2 更新P new 上的数据,该数据反映了 Read1 之后的状态(即,较早的状态反映了 Read1 读取的数据)。 |
读取关注内容“本地”并写入关注内容\ {w: 1}
在因果一致的会话中使用读关注点"local"和写关注点{ w: 1 }不能保证因果一致性。
❌自己读❌单调读 read 单调写❌写跟随读
在某些情况下(但不一定在所有情况下),此组合可以满足所有四个因果一致性保证。
方案 5(“本地”关注问题和“ {w: 1}
”关注问题)
在此过渡期间,由于P
old 和P
new 都可以满足{ w: 1 }写问题的写操作,因此 Client 机会话可以成功发出以下操作序列,但因果关系不一致:
Sequence | Example |
---|---|
1.写入关注度为{ w: 1 }至P 老的 1 | |
2.阅读关注度为"local"至 S 1 的 13.写 2,写关注点{ w: 1 }至 P 新4.以关注点"local"到 S 3 读取 2 | 对于项目A ,将qty 更新为50 。阅读项目 A 。对于 qty 小于或等于50 的项目,将 restock 更新为true 。阅读项目 A 。 |
❌自己写 | Read2 从S 3 读取数据,该数据仅反映 Write2 之后的状态,而不反映 Write1 之后是 Write2 的状态。 |
❌单调读 | Read2 从S 3 读取数据,该数据不反映 Read1 之后的状态(即,较早的状态不反映 Read1 读取的数据)。 |
❌单调写 | Write2 更新P new 上的数据,这些数据不反映 Write1 之后的状态。 |
❌写跟随读 | Write2 更新P new 上的数据,该数据不反映 Read1 之后的状态(即,较早的状态不反映 Read1 读取的数据)。 |
读取关注点“本地”并写入关注点“多数”
在因果一致会话中使用读关注点"local"和写关注点"majority"提供了以下因果一致性保证:
❌自己读❌单调读 read 单调写❌写跟随读
在某些情况下(但不一定在所有情况下),此组合可以满足所有四个因果一致性保证。
方案 6(“关注本地”和“关注多数”)
在此过渡期间,因为只有P
new 可以满足{ w: "majority" }写问题,所以 Client 端会话可以成功发出以下操作序列,但因果关系不一致:
Sequence | Example |
---|---|
1.将关注点"majority"写为 1 到P 新 | |
2.阅读关注度为"local"至 S 1 的 13.写 2,写关注点"majority"至 P 新4.以关注点"local"到 S 3 读取 2 | 对于项目A ,将qty 更新为50 。阅读项目 A 。对于 qty 小于或等于50 的项目,将 restock 更新为true 。阅读项目 A 。 |
❌阅读自己的文章。 | Read1 从S 1 读取未反映 Write11 之后状态的数据。 |
❌单调读。 | Read2 从S 3 读取数据,该数据不反映 Read1 之后的状态(即,较早的状态不反映 Read1 读取的数据)。 |
✅单调写 | Write2 更新P new 上的数据,该数据反映 Write1 之后的状态。 |
❌写跟读。 | Write2 更新P new 上的数据,该数据不反映 Read1 之后的状态(即,较早的状态不反映 Read1 读取的数据)。 |