Docs 菜单
Docs 主页
/
MongoDB Manual
/ / /

因果一致性以及读关注和写关注

通过 MongoDB 的因果一致客户端会话,不同的读关注和写入关注组合可提供不同的因果一致性ACID 一致性保证。

下表列出了不同组合可以提供的具体保证:

读关注 (read concern)
写关注
读取自己的写入
单调读取
单调写入
读取后写入

如果您希望在数据持久性方面实现因果一致性,那么,从表中可以看出,只有具有 "majority" 读关注的读操作和具有 "majority" 写关注的写操作才能确保实现所有四种因果一致性保证。也就是说,因果一致的客户端会话只能在以下情况下保证因果一致性:

  • 具有 "majority" 读关注的读取操作;换句话说,读取操作返回的数据已被大多数副本集成员确认并且具持久性。

  • 具有 "majority" 写关注的写入操作;换句话说,请求确认该操作已应用于大多数副本集投票成员的写入操作。

如果要实现因果一致性而无需数据持久性(这意味着写入可能会回滚),那么带有 { w: 1 } 写关注的写入操作也可提供因果一致性。

注意

在某些情况下,读关注和写关注的其他组合也可能满足所有四种因果一致性保证,但并非在所有情况下都是必需的。

读关注"majority"和写关注"majority"确保即使在副本集中的两个成员暂时认为自己是主节点的情况下(例如网络分区),这四个因果一致性保证仍然有效。虽然两个主节点都可以完成具有{ w: 1 }写关注的写入,但只有一个主节点能够完成具有"majority"写关注的写入。

例如,假设网络分区将一个包含五个成员的副本集进行了分隔:

网络分区:在一侧选举了新的主节点,但旧的主节点尚未下线。

例子

通过上述分区

  • 具有"majority"写关注的写入可以在P new 上完成,但无法在P old 上完成。

  • 具有 { w: 1 } 写关注的写入操作可在 P oldP new 上完成。但是,一旦这些成员与副本集的其余部分重新建立通信,对 P old 的写入(以及复制到 S 1 的写入)便会进行回滚。

  • "majority" 写关注在 P new 上成功写入后,带有 "majority" 读关注的因果一致性读取可以观察到 P newS2S3 上的写入。一旦 P oldS1 可与副本集的其余部分通信并与副本集的其他成员同步,读取同样可以观察到这两个成员上的写入。分区时对 P old 进行的任何写入和/或到 S 1 的任何复制都会回滚。

为了说明读关注和写关注要求,以下场景让客户端向副本集发出一系列具有各种读关注和写关注组合的操作:

  • 读关注 "majority" 和写关注 "majority"

  • 读关注 "majority" 和写关注 {w: 1}

  • 读关注 "local" 和写关注 "majority"

  • 读关注 "local" 和写关注 {w: 1}

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

✅ 读取自己的写入 ✅ 单调读取 ✅ 单调写入 ✅ 写入跟随读取

注意

场景 1(读关注“majority”和写关注“majority”)

在具有两个主节点的过渡期内,由于只有 P new 可以完成写入(写关注为 { w: "majority" })写关注,因此客户端会话可以成功发出以下操作序列:

顺序
例子
1. Write 1 with write concern "majority" to P new
2. Read 1 with read concern "majority" to S 2
3. Write 2 with write concern "majority" to P new

4. Read 2 with read concern "majority" to S 3
For item A, update qty to 50.
Read item A.
For items with qty less than or equal to 50,
update restock to true.
Read item A.
使用读关注多数和写关注多数的两个主节点的状态
点击放大
读取自己的写入
Read 1 reads data from S 2 that reflects a state after Write 1.
Read 2 reads data from S 3 that reflects a state after Write 1 followed by Write 2.
单调读取
读取 2 会从 S 3 读取数据,该数据反映读取 1 之后的状态。
单调写入
写入 2 会更新有关 P new 的数据,而该数据反映写入 1 之后的状态。
读取后写入
写入 2 会更新有关 P new(可反映读取 1 之后的数据状态)的数据(即,较早的状态可反映读取 1 所读取的数据)。

注意

场景 2(读关注“majority”和写关注“majority”)

假设有一备用序列,其中带读关注 "majority" 的读取 1 会路由到 S 1

顺序
例子
1. Write 1 with write concern "majority" to P new
2. Read 1 with read concern "majority" to S 1
3. Write 2 with write concern "majority" to P new

4. Read 2 with with read concern "majority" to S 3
For item A, update qty to 50.
Read item A.
For items with qty less than or equal to 50,
update restock to true.
Read item A.

在此序列中,仅当多数提交点在 P old 上前进后,读取 1 才能返回。在 P oldS 1 可与副本集的其余部分通信之前,不会出现此情况;此时,P old 已退出(如果还没有),且这两个成员会与副本集的其他成员同步(包括写入 1)。

读取自己的写入
Read 1 reflects a state of data after Write 1, albeit after the network partition has healed and the member has sync'ed from the other members of the replica set.
Read 2 reads data from S 3 that reflects a state after Write 1 followed by Write 2.
单调读取
读取 2 会从 S 3 读取数据,而该数据反映了读取 1 之后的状态(即,较早的状态会反映在读取 1 所读取的数据中)。
单调写入
写入 2 会更新有关 P new 的数据,而该数据反映写入 1 之后的状态。
读取后写入
写入 2 会更新有关 P new(可反映读取 1 之后的数据状态)的数据(即,较早的状态可反映读取 1 所读取的数据)。

在因果一致性会话中使用读关注 "majority" 和写关注 { w: 1 } 可提供以下因果一致性保证(如果要实现因果一致性和数据持久性)

❌ 读取自己的写入 ✅ 单调读取 ❌ 单调写入 ✅ 写入跟随读取

如果要实现因果一致性而无需数据持久性

✅ 读取自己的写入 ✅ 单调读取 ✅ 单调写入 ✅ 写入跟随读取

注意

场景 3(读关注“majority”和写关注 {w: 1})

在有两个主节点的过渡时期,由于 P oldP new 都可以完成写入(写关注 { w: 1 }),因此客户端会话可以成功发出以下操作序列,但无法实现因果一致性(如果您希望实现因果一致性和数据持久性):

顺序
例子
1. Write 1 with write concern { w: 1 } to P old
2. Read 1 with read concern "majority" to S 2
3. Write 2 with write concern { w: 1 } to P new

4. Read 2 with with read concern "majority" to S 3
For item A, update qty to 50.
Read item A.
For items with qty less than or equal to 50,
update restock to true.
Read item A.
使用读关注 majority 和写关注 1 的两个主节点的数据状态
点击放大

在该序列中,

  • 直到多数提交点在 P new 上前进到写入 1 的时间之后,读取 P1 才能返回。

  • 直到多数提交点在 P new 上前进到 Write 2 的时间之后,Read P2 才能返回。

  • 当网络分区恢复后,写入 1 将回滚。

如果想要实现因果一致性和数据持久性

读取自己的写入
读取 1S 1 读取数据,该数据不反映写入 1 之后的状态。
单调读取
读取 2 会从 S 3 读取数据,而该数据反映了读取 1 之后的状态(即,较早的状态会反映在读取 1 所读取的数据中)。
单调写入
写入 2 会更新有关 P new 的数据,而该数据不反映写入 1 之后的状态。
读取后写入
写入 2 会更新有关 P new 的数据,而该数据反映了读取 1 之后的状态(即,其较早的状态可反映读取 1 所读取的数据)。

如果想要实现因果一致性而无需数据持久性

读取自己的写入
读取1 从反映与写入S2 等效的状态的1 读取数据,然后回滚写入1
单调读取
读取 2 会从 S 3 读取数据,而该数据反映了读取 1 之后的状态(即,较早的状态会反映在读取 1 所读取的数据中)。
单调写入
写入2会更新有关 P 的数据,这相当于在写入1之后回滚写入1
读取后写入
写入 2 会更新有关 P new 的数据,而该数据反映了读取 1 之后的状态(即,其较早的状态可反映读取 1 所读取的数据)。

注意

场景 4(读关注“majority”和写关注 {w: 1})

假设有一备用序列,其中带读关注 "majority" 的读取 1 会路由到 S 1

顺序
例子
1. Write 1 with write concern { w: 1 } to P old
2. Read 1 with read concern "majority" to S 1
3. Write 2 with write concern { w: 1 } to P new

4. Read 2 with with read concern "majority" to S 3
For item A, update qty to 50.
Read item A.
For items with qty less than or equal to 50,
update restock to true.
Read item A.

在该序列中,

  • S 1 多数提交点前进之前,Read 1 无法返回。在 P oldS 1 可以与副本集的其余部分通信之前,这种情况不会发生。此时,P old 已退出(如果尚未退出),Write 1P oldS 1 回滚,并且两个成员与副本集的其他成员同步。

如果想要实现因果一致性和数据持久性

读取自己的写入
读取 1 所读取的数据不反映写入 1(已回滚)的结果。
单调读取
读取 2 会从 S 3 读取数据,而该数据反映了读取 1 之后的状态(即,其较早的状态可反映读取 1 所读取的数据)。
单调写入
写入 2 会更新有关 P new 的数据,而该数据不反映写入 1(先于写入 2,但已回滚)之后的状态。
读取后写入
写入 2 会更新有关 P new 的数据,而该数据反映了读取 1 之后的状态(即,其较早的状态可反映读取 1 所读取的数据)。

如果想要实现因果一致性而无需数据持久性

读取自己的写入
读取 1 返回的数据反映了写入 1 的最终结果,因为写入 1 最终会回滚。
单调读取
读取 2 会从 S 3 读取数据,而该数据反映了读取 1 之后的状态(即,其较早的状态可反映读取 1 所读取的数据)。
单调写入
写入2会更新有关 P 的数据,这相当于在写入1之后回滚写入1
读取后写入
写入 2 会更新有关 P new 的数据,而该数据反映了读取 1 之后的状态(即,其较早的状态可反映读取 1 所读取的数据)。

在因果一致会话中使用读关注 "local" 和写关注 { w: 1 } 无法保证因果一致性。

❌ 读取自己的写入 ❌ 单调读取 ❌ 单调写入 ❌ 写入跟随读取

在某些情况下,这一组合可能满足所有四种因果一致性保证,但并非在所有情况下都是必需的。

注意

场景 5(读关注“local”和写关注 {w: 1})

在此过渡期内,由于 P oldP new 均可以完成具有 { w: 1 } 写关注的写入,因此客户端会话可以成功发出以下操作序列,但在因果关系上不一致:

顺序
例子
1. Write 1 with write concern { w: 1 } to P old
2. Read 1 with read concern "local" to S 1
3. Write 2 with write concern { w: 1 } to P new

4. Read 2 with read concern "local" to S 3
For item A, update qty to 50.
Read item A.
For items with qty less than or equal to 50,
update restock to true.
Read item A.
使用读关注本地和写关注 1 的两个主节点的状态
点击放大
❌ 读取自己的写入
读取 2 会从 S 3 读取数据,而该数据仅反映写入 2 之后的状态,而不反映写入 1(写入 2 紧随其后)之后的状态。
❌ 单调读取
读取 2 会从 S 3(不反映读取 1 之后的状态)读取数据(即,较早的状态不反映读取 1 所读取的数据)。
❌ 单调写入
写入 2 会更新有关 P new 的数据,而该数据不反映写入 1 之后的状态。
❌ 读取后写入
写入 2 会更新有关 P new 的数据,而该数据不会反映了读取 1 之后的状态(即,其较早的状态不会反映读取 1 所读取的数据)。

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

❌ 读取自己的写入 ❌ 单调读取 ✅ 单调写入 ❌ 读取后写入

在某些情况下,这一组合可能满足所有四种因果一致性保证,但并非在所有情况下都是必需的。

注意

场景 6(读关注“local”和写关注“majority”)

在此过渡期内,由于只有 P new 可以完成具有 { w: "majority" } 写关注的写入,因此客户端会话可以成功发出以下操作序列,但在因果关系上不一致:

顺序
例子
1. Write 1 with write concern "majority" to P new
2. Read 1 with read concern "local" to S 1
3. Write 2 with write concern "majority" to P new

4. Read 2 with read concern "local" to S 3
For item A, update qty to 50.
Read item A.
For items with qty less than or equal to 50,
update restock to true.
Read item A.
使用读关注本地和写关注多数的两个主节点的状态
点击放大
❌ 读取自己的写入。
读取 1S 1 读取数据,该数据不反映写入 1 之后的状态。
❌单调读取。
读取 2 会从 S 3(不反映读取 1 之后的状态)读取数据(即,较早的状态不反映读取 1 所读取的数据)。
✅ 单调写入
写入 2 会更新有关 P new 的数据,而该数据反映写入 1 之后的状态。
读取后写入。
写入 2 会更新有关 P new 的数据,而该数据不会反映了读取 1 之后的状态(即,其较早的状态不会反映读取 1 所读取的数据)。

后退

读取隔离性、一致性和新近度