Docs 菜单

Docs 主页开发应用程序MongoDB Manual

常见问题解答:并发

在此页面上

MongoDB 允许多个客户端读取和写入相同的数据。为确保一致性,MongoDB 使用锁定和并发控制来防止多个客户端同时修改相同的数据。对单个文档的写入要么完整发生,要么根本不发生,并且客户端始终看到一致的数据。

MongoDB 使用多粒度锁定 [1],允许操作在全局、数据库或集合级别上锁定,并允许各个存储引擎在集合级别以下(例如,WiredTiger 中的文档级别)实施自己的并发控制。

MongoDB 使用读写锁,允许并发读取者共享对资源(例如数据库或集合)的访问。

除了用于读取的共享 (S) 锁定模式和用于写入操作的独占 (X) 锁定模式之外,意图共享 (IS) 和意图独占 (IX) 模式表示使用更细粒度的锁来读取或写入资源的意图。按一定粒度锁定时,所有更高级别都使用意向锁进行锁定。

例如,当锁定一个集合进行写入(使用模式 X)时,相应的数据库锁和全局锁都必须以意向独占 (IX) 模式锁定。单个数据库可以同时以 IS 和 IX 模式锁定,但独占 (X) 锁不能与任何其他模式共存,共享 (S) 锁只能与意向共享 (IS) 锁共存。

锁是公平的,读取和写入的锁请求都会按顺序排队。然而,为了优化吞吐量,批准一个锁请求时,也会同时批准所有其他兼容的锁请求,这可能会导致在执行冲突的锁请求之前释放锁。例如,当一个 X 锁刚被释放并且冲突队列包含这些锁:

IS → IS → X → X → S → IS

根据严格的先进先出 (FIFO) 顺序,仅批准前两个 IS 模式。相反,MongoDB 实际上会批准所有 IS 和 S 模式,一旦它们全部耗尽,将批准 X,即使新的 IS 或 S 请求同时已在队列中。由于授予总是将队列中的所有其他请求移至前面,因此不可能出现任何请求的饥饿。

db.serverStatus()db.currentOp() 输出中,锁模式表示如下:

锁模式
说明
R
代表共享(S)锁。
W
代表独占 (X) 锁。
r
代表意向共享(IS)锁。
w
代表意图独占 (IX) 锁。
[1] 请参阅有关 多粒度锁定 的 Wikipedia 页面 以了解更多信息。

对于大多数读取和写入操作,WiredTiger 均使用乐观并发控制。WiredTiger 仅在全局、数据库和集合级别使用意向锁。当存储引擎检测到两个操作之间存在冲突时,其中一个操作会引发写入冲突,从而导致 MongoDB 以透明方式重试该操作。

某些全局操作(通常是涉及多个数据库的短期操作)仍需全局性的“实例范围”锁。其他一些操作(如 collMod)仍需数据库独占锁。

使用以下方法报告锁的锁利用率信息:

具体而言,ServerStatus 输入中的 locks 文档或 current operation reporting 中的 locks 字段可让您深入了解 mongod 实例中的锁类型和锁争用量。

db.serverStatus()db.currentOp() 输出中,锁模式表示如下:

锁模式
说明
R
代表共享(S)锁。
W
代表独占 (X) 锁。
r
代表意向共享(IS)锁。
w
代表意图独占 (IX) 锁。

要终止操作,请使用 db.killOp()

在某些情况下,读取和写入操作可能会产生锁。

长时间运行的读写操作(例如查询、更新和删除)在许多情况下都会产生锁。MongoDB 操作还可以在影响多个文档的写入操作中的各个文档修改之间产生锁。

对于支持文档级并发控制的存储引擎(例如 WiredTiger),访问存储器时不一定产生锁,因为全局、数据库和集合级别的意向锁不会阻止其他读取者和写入者。但是,操作将定期产生锁,例如:

  • 避免长期存储事务,因为事务可能需要在内存中保存大量数据;

  • 用作中断点,以便您可以终止长时间运行的操作;

  • 允许需要对集合进行独占访问的操作,例如索引/集合删除和创建。

下表列出一些操作及其为文档级锁定存储引擎使用的锁类型:

操作
数据库
Collection
发出查询
r (意向共享)
r (意向共享)
插入数据
w (意向独占)
w (意向独占)
删除数据
w (意向独占)
w (意向独占)
更新数据
w (意向独占)
w (意向独占)
执行聚合
r (意向共享)
r (意向共享)
创建索引(前景)
W (独占)
创建索引(背景)
w (意向独占)
w (意向独占)
listCollections

r (意向共享)

4.0 版本中的更改

map-reduce
W (独占)和 R(共享)
w (意向独占)和 r(意向共享)

某些管理命令可以长时间独占地锁定数据库。对于大型数据库部署,可以考虑将 mongod 实例脱机,这样客户端就不会受到影响。例如,如果 mongod副本集的一部分,则将 mongod 脱机,让副本集的其他节点在执行维护期间处理请求。

这些管理操作需要在数据库级别长时间使用独占锁:

此外,renameCollection 命令和相应的 db.collection.renameCollection() shell 方法根据 MongoDB 的版本采用以下锁:

命令
MongoDB 4.2.2 或更高版本
MongoDB 4.2.0 - 4.2.1
MongoDB 4.0.X 及之前版本
renameCollection 数据库命令

如果重命名同一个数据库中的集合,renameCollection 命令会对源集合和目标集合使用独占 (W) 锁。

如果目标命名空间与源集合位于不同的数据库,则在跨数据库重命名集合时,renameCollection 命令会在目标数据库上使用独占 (W) 锁,并在完成之前阻止对该数据库进行其他操作。

如果重命名同一个数据库中的集合,renameCollection 命令会对源集合和目标集合使用独占 (W) 锁。

如果目标命名空间与源集合位于不同的数据库,则在跨数据库重命名集合时,renameCollection 命令采用全局独占 (W) 锁,并在完成之前阻止其他操作。

在 MongoDB 4.2 之前,renameCollection 命令在同一数据库内重命名时对数据库采用独占 (W) 锁。
renameCollection() shell 助手方法
如果重命名同一个数据库中的集合,renameCollection() 方法会对源集合和目标集合使用独占 (W) 锁。
(与 MongoDB 4.2.2 或更高版本的行为相同)
在 MongoDB 4.2 之前,当在同一数据库中重命名时,renameCollection() 方法会对数据库采用独占 (W) 锁。

这些管理操作锁定数据库,但锁定时间很短:

版本 4.2 中进行了更改

这些管理操作需要在数据集级别采用独占锁:

  • create 命令和相应的 db.createCollection()db.createView() shell 方法

  • createIndexes 命令和相应的 db.collection.createIndex()db.collection.createIndexes() shell 方法

  • drop 命令和相应的 db.collection.drop() shell 方法

  • dropIndexes 命令和相应的 db.collection.dropIndex()db.collection.dropIndexes() shell 方法

  • renameCollection 命令和相应的 db.collection.renameCollection() shell 方法根据版本采用以下锁:

    • 对于 renameCollectiondb.collection.renameCollection():如果重命名同一数据库中的集合,则该操作对源集合和目标集合采用独占 (W) 锁。在 MongoDB 4.2 之前,在同一数据库中重命名时,该操作会对数据库使用独占 (W) 锁。

    • 针对 renameCollection:如果目标命名空间与源集合位于不同的数据库,则锁定行为取决于版本:

      • MongoDB 4.2.2 及更高版本在跨数据库重命名集合时,该操作会对目标数据库使用独占 (W) 锁,并在目标数据库完成之前阻止对该数据库进行其他操作。

      • MongoDB 4.2.1 及更早版本在跨数据库重命名集合时,该操作采用全局独占 (W) 锁,并在完成之前阻止其他操作。

  • reIndex 命令和相应的 db.collection.reIndex() shell 方法根据版本采用以下锁:

    • 对于 MongoDB 4.2.2 及更高版本,这些操作对集合获得独占 (W) 锁,在完成之前阻止集合上的其他操作。

    • 对于 MongoDB 4.0.0 到 4.2.1,这些操作采用全局独占 (W) 锁并在完成之前阻止其他操作。

  • 根据版本的不同,replSetResizeOplog 命令会使用以下锁:

    • 对于 MongoDB 4.2.2 及更高版本,此操作对 oplog 集合采用独占 (W) 锁,并在完成之前阻止对该集合进行其他操作。

    • 对于 MongoDB 4.2.1 及更早版本,此操作采用全局独占他 (W) 锁,并在完成之前阻止其他操作。

在 MongoDB 4.2 之前,这些操作对数据库采取独占锁,在该操作完成之前,阻止对数据库及其集合的所有操作。

这些 MongoDB 操作可能会获取并持有多个数据库的锁:

操作
行为

版本 4.2 中进行了更改

对于 MongoDB 4.0.0 到 4.2.1,这些操作采用全局独占 (W) 锁并在完成之前阻止其他操作。

从 MongoDB 4.2.2 开始,这些操作仅获取独占 (W) 集合锁,而不是全局独占锁。

在 MongoDB 4.0 之前,这些操作获得独占 (W) 数据库锁。

版本 4.2 中进行了更改

对于 MongoDB 4.2.1 及更早版本,在数据库之间重命名集合时,此操作会获取全局独占 (W) 锁,并在完成之前阻止其他操作。

从 MongoDB 4.2.2 开始,此操作仅获取目标数据库上的独占 (W) 锁、源数据库上的意向共享 (R) 锁以及源集合上的共享 (S) 锁,不会获得全局独占锁。

版本 4.2 中进行了更改

对于 MongoDB 4.2.1 及更早版本,此操作获得全局独占 (W) 锁,在完成之前阻止其他操作。

从 MongoDB 4.2.2 开始,此操作仅获得 oplog 集合的独占 (W) 锁,而非全局独占锁。

分片通过在多个 mongod 实例上分配集合来改善并发性,允许分片服务器(特别是 mongos 进程)与下游 mongod 实例并行运行。

在分片集群中,锁适用于每个单独的分片,而不是整个集群;即每个mongod实例独立于分片集群中的其他实例,并使用自己的。对一个mongod实例的操作不会阻止对任何其他实例的操作。

对于副本集,当 MongoDB 写入主节点上的集合时,MongoDB 还会写入主节点的 oplog(这是 local 数据库中的一个特殊集合)。因此,MongoDB 必须锁定该集合的数据库和 local 数据库。mongod 必须同时锁定这两个数据库,以保持数据库一致,并确保写入操作(即使有复制)是全有或全无操作。

写入副本集时,锁的范围适用主节点

复制中,MongoDB 不会将写入串行应用于从节点。从节点分批收集 oplog 条目,然后并行应用这些批次。写入操作按照它们在 oplog 中出现的顺序进行应用。

从 MongoDB 4.0 开始,如果从节点正在进行复制,则读取从数据的 WiredTiger 快照中读取的目标从节点。这样读取与复制可同时进行,又能保证数据的一致性。在 MongoDB 4.0 之前,任何正在进行的复制完成之前,从节点上的读取操作将被阻止。更多信息,请参阅多线程复制

由于单个文档可以包含相关数据(否则这些数据将在关系型模式中的不同父子表中建模),因此 MongoDB 的原子性单文档操作已经提供了充足的事务语义,能够满足大多数应用程序对数据完整性的需求。可以在单个操作中写入一个或多个字段,包括对多个子文档和数组元素进行更新。MongoDB 提供的保证可确保文档更新时实现完全隔离;任何错误都会导致操作回滚,以便客户端收到文一致的文档视图。

对于需要对多个文档(在单个或多个集合中)原子性读取和写入的情况,MongoDB 支持分布式事务,包括副本集和分片集群上的事务。

有关详细信息,请参阅事务。

重要

在大多数情况下,与单文档写入操作相比,分布式事务会产生更高的性能成本,并且分布式事务的可用性不应取代有效的模式设计。在许多情况下,非规范化数据模型(嵌入式文档和数组)仍然是数据和使用案例的最佳选择。换言之,对于许多场景,适当的数据建模将最大限度地减少对分布式事务的需求。

有关其他事务使用注意事项(如运行时间限制和 oplog 大小限制),另请参阅生产注意事项

根据读关注,客户端可在写入操作持久化之前看到写入结果:要控制读取的数据是否可以回滚,客户端可以使用 readConcern 选项。

版本 5.0 中的新增功能

如果当另一个操作在集合上具有独占 (X) 写锁时,无锁读取操作不会受到阻止,则该无锁读取操作可立即运行。

从 MongoDB 5.0 开始,当另一个操作在集合上持有独占 (X) 写锁时,以下读取操作不会受到阻止:

写入集合时,mapReduceaggregate 持有意向排他 (IX) 锁。因此如果集合上已经持有排他 X 锁,mapReduceaggregate 写操作将被阻塞。

有关信息,请参阅:

← 常见问题解答:索引