因果一致性和读写问题

使用 MongoDB 的因果一致的 Client 会话,读写问题的不同组合提供了不同的因果一致性保证。如果定义因果一致性以表示耐久性,则下表列出了各种组合提供的特定保证:

Read Concern Write Concern 阅读自己的文章 Monotonic reads Monotonic writes 写跟读
"majority" "majority"
"majority" { w: 1 }
"local" { w: 1 }
"local" "majority"

如果因果一致性表示持久性,那么从表中可以看出,只有具有"majority"读关注点的读取操作和具有"majority"写入关注点的写入操作才能保证所有四个因果一致性保证。也就是说,因果一致的 Client 会话仅能保证以下情况的因果一致性:

如果因果一致性并不意味着持久性(即,写操作可能会回滚),则具有{ w: 1 }写入关注点的写操作也可以提供因果一致性。

Note

在某些情况下(但不一定在所有情况下),读和写关注点的其他组合也可以满足所有四个因果一致性保证。

读关注点"majority"和写关注点"majority"确保四个因果一致性保证即使在情况(例如带有网络分区的情况)中也是如此,因为副本集中的两个成员暂时地认为它们是主要的。尽管两个主数据库都可以完成{ w: 1 }写关注,但只有一个主数据库能够完成"majority"写关注。

例如,考虑网络分区划分五个成员副本集的情况:

With the above partition

  • 具有"majority"写关注的写操作可以在P新完成,但是不能在P旧完成。

  • 具有{ w: 1 }写关注的写可以在P旧或P新上完成。但是,一旦P old 的写入(以及复制到S1 的写入)回滚,则这些成员恢复与副本集其余部分的通信。

  • 成功对P new 进行"majority"写关注后,与"majority"读关注因果一致的读操作可以观察到P new,S 2 和S 3 上的写操作。一旦读入,读操作也可以观察到P old 和S1 上的写操作。与副本集的其余部分进行通信,并与副本集的其他成员进行同步。在分区期间对P old 和/或复制到S1 的所有写操作都会回滚。

Scenarios

为了说明读写关注点要求,在以下情况下,Client 端向 Client 端发出了一系列操作,并对副本集进行了读写关注点的各种组合:

阅读关注“多数”并写关注“多数”

在因果一致会话中使用读关注点"majority"和写关注点"majority"提供了以下因果一致性保证:

✅自己读✅单调读 read 单调写✅写跟随读

方案 1(读取关注点“多数”和写入关注点“多数”)

在具有两个主键的过渡期间,由于只有P new 可以满足{ w: "majority" }写问题,因此 Client 端会话可以成功发出以下操作序列:

Sequence Example
1.将关注点"majority"写为 1 到P

2.读为 1,读意为"majority"S 2
3.写 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 更新Pnew 上的数据,该数据反映了 Read1 之后的数据状态(即,较早的状态反映了 Read1 读取的数据)。

方案 2(读取关注点“多数”和写入关注点“多数”)

考虑一个替代序列,其中具有读关注"majority"的 Read1 路由到S 1:

Sequence Example
1.将关注点"majority"写为 1 到P

2.阅读关注度为"majority"S 1 的 1
3.写 2,写关注点"majority"P
4.以关注点"majority"S 3 读取 2
对于项目A,将qty更新为50
阅读项目A
对于qty小于或等于50的项目,
restock更新为true
阅读项目A

按照这种 Sequences,直到多数提交点在P old 上前进,Read1 才返回。直到P old 和S1 可以与副本集的其余部分通信时,才会发生这种情况。那时P old 已下台(如果尚未退出),并且两个成员与副本集的其他成员同步(包括 Write1)。

读自己写的东西 读 1 反映了写 1 1 之后的数据状态,尽管在网络分区已修复并且该成员已与副本集的其他成员同步之后。


读取 2 从S 3 读取数据,该数据反映了 Write1 1 之后是 Write 2 之后的状态。
|✅ 单声道读取 | Read2 从S 3 读取反映 Read1 之后状态的数据(即,较早的状态反映在 Read1 读取的数据中)。
|✅ 单调写入 | Write2 更新P new 上的数据,以反映 Write1 之后的状态。
|✅ Writes 跟着读取 | Write2 更新Pnew 上的数据,该数据反映了 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 2
3.写 2,写关注点{ w: 1 }P
4.以关注点"majority"S 3 读取 2
对于项目A,将qty更新为50
阅读项目A
对于qty小于或等于50的项目,
restock更新为true
阅读项目A

按照这个 Sequences

如果因果一致性意味着持久性

读自己写的东西 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 的 1
3.写 2,写关注点{ w: 1 }P
4.以关注点"majority"S 3 读取 2
对于项目A,将qty更新为50
阅读项目A
对于qty小于或等于50的项目,
restock更新为true
阅读项目A

按此 Sequences:

如果因果一致性意味着持久性

读自己写的东西 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 的 1
3.写 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 更新Pnew 上的数据,该数据不反映 Read1 之后的状态(即,较早的状态不反映 Read1 读取的数据)。

读取关注点“本地”并写入关注点“多数”

在因果一致会话中使用读关注点"local"和写关注点"majority"提供了以下因果一致性保证:

❌自己读❌单调读 read 单调写❌写跟随读

在某些情况下(但不一定在所有情况下),此组合可以满足所有四个因果一致性保证。

方案 6(“关注本地”和“关注多数”)

在此过渡期间,因为只有P new 可以满足{ w: "majority" }写问题,所以 Client 端会话可以成功发出以下操作序列,但因果关系不一致:

Sequence Example
1.将关注点"majority"写为 1 到P

2.阅读关注度为"local"S 1 的 1
3.写 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 更新Pnew 上的数据,该数据不反映 Read1 之后的状态(即,较早的状态不反映 Read1 读取的数据)。
首页