一致性模型
MongoDB 读操作可以提供不同的一致性保证,具体取决于您读取数据的位置和方式。在为应用程序选择 ACID 一致性保证时,请区分以下模型:
过时的从节点(secondary node from replica set)读取:由于从节点(secondary node from replica set)是异步复制的,因此通过非
primary读取偏好(read preference)定向到从节点(secondary node from replica set)的读取可能会返回落后于主节点 (primary node in the replica set)的数据。从节点(secondary node from replica set)总是按照与主节点 (primary node in the replica set)相同的顺序应用写入,因此读取的数据可能是过时的,但绝不会乱序。这反映了 MongoDB 使用的最终一致性模型。Causal consistency: causally consistent client sessions ensure that a sequence of causally related read and write operations observes a consistent order, even across different members of a replica set.
快照和已提交读隔离性:读关注(read concern)
"snapshot"返回特定时间点的数据,而读关注(read concern)"majority"仅返回由大多数副本集节点确认的数据。两者都提供比从从节点(secondary node from replica set)进行非协调读取更强的隔离性ACID 一致性保证。
隔离性保证
读未提交(Read Uncommitted)
根据读关注,客户端可在写入操作持久化之前看到写入结果:
无论写入操作的写关注级别如何,使用
"local"或"available"读关注(read concern)的其他客户端均可在写入操作被发起它的客户端确认之前,看到该操作的结果。使用
"local"或"available"读关注的客户端可读取数据,而这些数据后续可能会在副本集故障转移期间进行回滚。
对于多文档事务中的操作,当事务提交时,该事务中进行的所有数据更改都将保存并在事务外部可见。换言之,一个事务不会在回滚其他事务的同时提交某些更改。
在事务进行提交前,在事务中所做的数据更改在事务外不可见。
不过,当事务写入多个分片时,并非所有外部读取操作都需等待已提交事务的结果在各个分片上可见。例如,如果事务已提交并且写入 1 在分片 A 上可见,但写入 2 在分片 B 上尚不可见,则读关注 "local" 处的外部读取可以在不看到写入 2 的情况下读取写入 1 的结果。
读取未提交是默认隔离性级别,适用于 mongod 独立运行的实例、副本集和集群。
读取未提交和单个文档的原子性
写入操作对于单个文档来说是原子的;即,如果写入正在更新文档中的多个字段,则读取操作将永远不会看到仅更新了某些字段的文档。然而,尽管客户端可能看不到部分更新的文档,但未提交读取意味着并发读取操作在更改持久性之前仍可能看到更新的文档。
使用独立的 mongod 实例时,可以对单个文档的一组读取和写入操作执行序列化。使用副本集时,只有在没有回滚的情况下才能对单个文档的一组读取和写入操作执行序列化。
读取未提交和多文档写入
当单个写操作(例如 db.collection.updateMany())修改了多份文档,则每份文档的修改都是原子性的,但整个操作不是原子性的。
在执行多文档写入操作时,无论是通过单次写入操作还是多次写入操作,其他操作都可能会交错进行。
对于需要对多个文档(在单个或多个集合中)原子性读取和写入的情况,MongoDB 支持分布式事务,包括副本集和分片集群上的事务。
有关详细信息,请参阅事务。
重要
在大多数情况下,与单文档写入操作相比,分布式事务会产生更高的性能成本,并且分布式事务的可用性不应取代有效的模式设计。在许多情况下,非规范化数据模型(嵌入式文档和数组)仍然是数据和使用案例的最佳选择。换言之,对于许多场景,适当的数据建模将最大限度地减少对分布式事务的需求。
有关其他事务使用注意事项(如运行时间限制和 oplog 大小限制),另请参阅生产注意事项。
在不隔离多文档写操作的情况下,MongoDB 表现出以下行为:
非时间点读操作。假设读操作在时间 t 1 开始并开始读取文档。写操作会在稍后某个时间 t 2 提交对其中一份文档的更新。读取者可能会看到该文档的更新版本,因此看不到数据的时间点快照。
不可序列化的操作。假设读操作在时间 t 1 读取文档 d 1 并且写操作在随后的时间 t 3 更新d 1。该操作引入读写依赖关系,使得如果要进行序列化操作,则读取操作必须先于写入操作。此外,假设写入操作在时间 t 2 更新文档 d 2,并且读取操作在随后的时间 t 4 读取d 2。该操作引入写读依赖关系,这将要求在可序列化计划中读取操作在写入操作之后进行。这种依赖关系循环,使序列化性变得无法实现。
读取可能会遗漏在读取操作过程中更新的匹配文档。
游标快照
在某些情况下,MongoDB 游标可以多次返回同一文档。当游标返回文档时,其他操作可能会与查询交错进行。如果其中一个操作改变了查询所用索引的索引字段,那么游标可能会多次返回同一文档。
某些情况下,使用唯一索引的查询可能会返回重复值。如果使用唯一索引的游标与共享相同唯一值的文档的删除和插入交错,则该游标可能会从不同的文档两次返回相同的唯一值。
使用读取隔离性来提高一致性。要学习;了解更多信息,请参读关注(read concern) "snapshot"。
单调写入
默认情况下,MongoDB 为独立的 mongod 实例和副本集提供单调写入保证。
对于单调写入和分片集群,请参阅因果一致性。
不修改任何文档的写入称为无操作 noop 写入。无操作写入:
如果写入操作的过滤器不匹配任何文档,或者在应用写入后匹配的文档没有发生变化,则会发生这种情况。
请勿增加 optime 值。
返回
WriteResult.nModified等于0,这表明写入操作未修改任何文档。
To ensure monotonicity with no-operation writes, use causal consistency guarantees. For all other writes, the following sections describe the monotonicity guarantees.
实时顺序
对于主节点 (primary node in the replica set)上的写入操作,通过为读取发出"linearizable"读关注(read concern)和为写入发出"majority"写关注(write concern),可以使多个线程读取和写入单个文档,就像单个线程实时执行这些操作一样。这些读取和写入的最终安排是可线性化的。
因果一致性(Causal Consistency)
在逻辑上依赖于先前操作的操作具有因果关系。示例,删除与指定条件匹配的所有文档的写入与验证删除操作的后续读取之间存在因果关系。
通过因果一致的会话, MongoDB会按照因果关系的顺序执行因果操作。客户端观察到的结果与这些关系一致。
客户端会话和因果一致性保证
MongoDB通过客户端会话实现因果一致性。因果一致的会话可确保具有"majority"读关注(read concern)的读取操作和具有"majority"写关注(write concern)的写入操作在其顺序中反映因果关系。应用程序必须确保在客户端会话中一次只能有一个线程执行这些操作。
对于因果相关的操作:
客户端会启动客户端会话。
重要
客户端会话仅保证以下方面的因果一致性:
具有
"majority"读关注(read concern)的读取操作。返回的数据已得到大多数副本集成员的确认,并且是持久性的。具有
"majority"写关注(write concern)的写入操作。这些操作请求确认写入已应用于副本集的大多数投票节点。
有关因果一致性和各种读写关注的更多信息,请参阅因果一致性和读写关注。
当客户端发出具有
"majority"读关注(read concern)的读取操作和具有"majority"写关注(write concern)的写入操作时,客户端会在每个操作中包含会话信息。对于与会话关联的每个具有
"majority"读关注(read concern)的读取操作和具有"majority"写关注(write concern)的写入操作,即使操作出错, MongoDB也会返回操作时间和集群时间。客户端会话追踪 operation time 和集群时间。注意
MongoDB 不返回未确认 (
w: 0) 写操作的操作时间和集群时间。未确认的写并不意味着任何因果关系。MongoDB返回客户端会话中读取操作和已确认写入操作的操作时间以及集群时间。只有具有
"majority"读关注(read concern)的读取操作和具有"majority"写关注(write concern)的写入操作才能保证因果一致性。有关详细信息,请参阅因果一致性和读关注和写关注(write concern)。相关客户端会话会追踪这两个时间字段。
注意
Operations can be causally consistent across different sessions. MongoDB drivers and
mongoshprovide methods to advance the operation time and the cluster time for a client session. A client can advance the cluster time and the operation time of one client session to be consistent with the operations of another client session.
因果一致性保证
下表描述了因果一致性会话的因果一致ACID 一致性保证,这些会话使用"majority" 读关注(read concern)进行读取操作,使用"majority" 写关注(write concern)进行写入操作。
保证 | 说明 |
|---|---|
读取写入操作 | 读取操作反映了它们之前的写入操作的结果。 |
单调读取 | 读取操作不会返回与特定数据状态(在某一先前读取操作之前出现的状态)相对应的结果。 例如,如果在会话中:
然后读取 2 无法返回写入 1 的结果。 |
单调写入 | 必须在其他写入之前并且会在其他写入之前执行的写入操作。 例如,如果在会话中写入 1 必须先于写入 2,则写入 2 时的数据状态必须反映写入 1 后数据的状态。其他写入可以在写入 1 和写入 2 之间交错进行,但写入 2 不能在写入 1 之前发生。 |
读取后写入 | 读操作之后必须发生的写操作在这些读操作之后执行。换言之,写入时的数据状态必须包含先前读操作的数据状态。 |
读取偏好
这些ACID 一致性保证适用于MongoDB 部署的所有节点。示例,在因果一致的会话中,如果您发出具有"majority"写关注(write concern)的写入,然后从具有读取偏好(read preference)secondary和"majority"读关注(read concern)的从从节点(secondary node from replica set)发出读取操作,则读取操作反映了数据库在写入操作。
隔离性
因果一致会话内的操作并不与会话外的操作隔离。如果并发写入操作在会话的写入和读取操作之间交错进行,则会话的读取操作可能会返回反映会话写入操作之后发生的写入操作的结果。
MongoDB 驱动程序
提示
应用程序必须确保在客户端会话中一次只有一个线程执行这些操作。
客户端需要针对 MongoDB 3.6 或更高版本更新 MongoDB 驱动程序:
Java 3.6 + Python 3.6+ C 1.9+ Go 1.8+ | C# 2.5 + 节点3.0 + Ruby 2.5+ Rust 2.1+ Swift 1.2 + | Perl 2.0 + PHPC 1.4 + Scala 2.2+ C++ 3.6.6 + |
示例
重要
因果一致会话仅保证具有"majority"读关注(read concern)的读取和具有"majority"写关注(write concern)的写入的因果一致性。
考虑一个维护各种项目的当前和历史数据的集合items。只有历史数据具有非空值的 end 日期。如果某个项目的 sku 值发生变化,则使用旧的 sku 值更新文档以添加 end 日期,然后插入包含当前 sku 值的新文档。使用因果一致的会话,确保更新发生在插入之前。
使用语言选择器设置此示例的语言。
要从其他客户端读取所有当前的 sku 值,请将集群时间和 operation time 提前以匹配其他会话。这可确保客户端与其他会话因果一致,并在两次写入后进行读取:
限制
以下操作构建内存中的结构,并且在因果一致上不一致:
操作 | 注意 |
|---|---|
| |
如果操作与因果一致的客户端会话关联,则返回错误。 | |
如果操作与因果一致的客户端会话关联,则返回错误。 | |
如果操作与因果一致的客户端会话关联,则返回错误。 | |
如果操作与因果一致的客户端会话关联,则返回错误。 | |