常见问题解答:并发
在此页面上
MongoDB 允许多个客户端读取和写入相同的数据。为了确保一致性,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] | 有关更多信息,请参阅维基百科上的多粒度锁定页面。 |
MongoDB 中的锁的粒度如何?
对于大多数读取和写入操作,WiredTiger 均使用乐观并发控制。WiredTiger 仅在全局、数据库和集合级别使用意向锁。当存储引擎检测到两个操作之间存在冲突时,其中一个操作会引发写入冲突,从而导致 MongoDB 以透明方式重试该操作。
某些全局操作(通常是涉及多个数据库的短期操作)仍需全局性的“实例范围”锁。在某些情况下,其他一些操作(例如renameCollection
)仍然需要独占数据库锁。
如何查看mongod
实例上的锁状态?
使用以下方法报告锁的锁利用率信息:
具体而言,ServerStatus 输入中的 locks
文档或 current operation reporting
中的 locks
字段可让您深入了解 mongod
实例中的锁类型和锁争用量。
在 db.serverStatus()
和 db.currentOp()
输出中,锁模式表示如下:
锁模式 | 说明 |
---|---|
R | 代表共享(S)锁。 |
W | 代表独占 (X) 锁。 |
r | 代表意向共享(IS)锁。 |
w | 代表意图独占 (IX) 锁。 |
要终止操作,请使用 db.killOp()
。
读取或写入操作是否会产生锁?
在某些情况下,读取和写入操作可能会产生锁。
长时间运行的读写操作(例如查询、更新和删除)在许多情况下都会产生锁。MongoDB 操作还可以在影响多个文档的写入操作中的各个文档修改之间产生锁。
对于支持文档级并发控制的存储引擎(例如 WiredTiger),访问存储器时不一定产生锁,因为全局、数据库和集合级别的意向锁不会阻止其他读取者和写入者。但是,操作将定期产生锁,例如:
避免长期存储事务,因为事务可能需要在内存中保存大量数据;
用作中断点,以便您可以终止长时间运行的操作;
允许需要对集合进行独占访问的操作,例如索引/集合删除和创建。
某些常见客户端操作会采用哪些锁?
下表列出一些操作及其为文档级锁定存储引擎使用的锁类型:
操作 | Database | Collection |
---|---|---|
发出查询 | r (意向共享) | r (意向共享) |
Insert data | w (意向独占) | w (意向独占) |
删除数据 | w (意向独占) | w (意向独占) |
Update data | w (意向独占) | w (意向独占) |
执行聚合 | r (意向共享) | r (意向共享) |
创建索引 | W (独占) | |
listCollections | r (意向共享) | |
map-reduce | W (独占)和R (共享) | w (意向独占)和r (意向共享) |
注意
创建索引需要在集合上使用独占 (W) 锁。但是,该锁不会在索引构建过程的整个过程中保留。
如需了解更多信息,请参阅在填充集合上构建索引。
哪些管理命令锁定数据库?
某些管理命令可以长时间独占锁定数据库。对于大型集群,请考虑将mongod
实例脱机,以便客户端不受影响。例如,如果mongod
是副本集的一部分,则使mongod
脱机,并在执行维护时让副本集的其他成员处理请求。
使用扩展锁的管理命令
这些管理操作需要在数据库级别长时间使用独占锁:
此外,renameCollection
命令和相应的 db.collection.renameCollection()
shell 方法采用以下锁:
命令 | 锁行为 |
---|---|
renameCollection 数据库命令 | 如果重命名同一个数据库中的集合, 如果目标命名空间与源集合位于不同的数据库,则在跨数据库重命名集合时, |
renameCollection() shell 助手方法 | renameCollection() 方法对源集合和目标集合采用独占 (W) 锁,并且无法在数据库之间移动集合。 |
哪些管理命令锁定集合?
这些管理操作需要在数据集级别采用独占锁:
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 方法根据版本采用以下锁:对于
renameCollection
和db.collection.renameCollection()
:如果重命名同一数据库中的集合,则该操作对源集合和目标集合采用独占 (W) 锁。仅适用于
renameCollection
:如果目标命名空间与源集合位于不同的数据库中,则在跨数据库重命名集合时,该操作会占用目标数据库的独占 (W) 锁,并阻塞该数据库上的其他操作,直到操作完成。
reIndex
命令和相应的db.collection.reIndex()
Shell 方法会获取对集合的独占 (W) 锁,并阻止对集合的其他操作,直到操作完成。replSetResizeOplog
命令会对oplog
集合采用独占 (W) 锁,并阻塞对该集合的其他操作,直到完成为止。
MongoDB 操作是否会锁定多个数据库?
这些 MongoDB 操作可能会获取并持有多个数据库的锁:
操作 | 行为 |
---|---|
这些操作仅获取独占 (W) 集合锁,而不是全局独占锁。 | |
跨数据库重命名集合时,此操作会获得目标数据库上的独占 (W) 锁、源数据库上的意向共享 (R) 锁以及源集合上的共享 (S) 锁。 重命名同一数据库中的集合时,该操作仅需要在源集合和目标集合上使用独占 (W) 锁。 | |
此操作仅获得 oplog 集合的独占 (W) 锁,而非全局独占锁。 |
分片如何影响并发性?
分片通过在多个 mongod
实例上分配集合来改善并发性,允许分片服务器(特别是 mongos
进程)与下游 mongod
实例并行运行。
在分片集群中,锁适用于每个分片,而非整个集群;换言之,每个 mongod
实例都独立于分片集群中的其他实例,并使用自己的锁。对一个 mongod
实例的操作不会阻塞对其他任何实例的操作。
并发性如何影响副本集主节点?
对于副本集,当 MongoDB 写入主节点上的集合时,MongoDB 还会写入主节点的 oplog(这是 local
数据库中的一个特殊集合)。因此,MongoDB 必须锁定该集合的数据库和 local
数据库。mongod
必须同时锁定这两个数据库,以保持数据库一致,并确保写入操作(即使有复制)是全有或全无操作。
并发性如何影响从节点?
在复制中,MongoDB 不会将写入串行应用于从节点。从节点分批收集 oplog 条目,然后并行应用这些批次。写入操作按照它们在 oplog 中出现的顺序进行应用。
如果从节点正在进行复制,则读取目标从节点从数据的WiredTiger快照中读取的值。这允许读取与复制同时发生,同时仍然保证数据的一致性。
MongoDB 是否支持事务?
由于单个文档可以包含相关数据(否则这些数据将在关系型模式中的不同父子表中建模),因此 MongoDB 的原子性单文档操作已经提供了充足的事务语义,能够满足大多数应用程序对数据完整性的需求。可以在单个操作中写入一个或多个字段,包括对多个子文档和数组元素进行更新。MongoDB 提供的保证可确保文档更新时实现完全隔离;任何错误都会导致操作回滚,以便客户端收到文一致的文档视图。
对于需要对多个文档(在单个或多个集合中)原子性读取和写入的情况,MongoDB 支持分布式事务,包括副本集和分片集群上的事务。
有关详细信息,请参阅事务。
重要
在大多数情况下,与单文档写入操作相比,分布式事务会产生更高的性能成本,并且分布式事务的可用性不应取代有效的模式设计。在许多情况下,非规范化数据模型(嵌入式文档和数组)仍然是数据和使用案例的最佳选择。换言之,对于许多场景,适当的数据建模将最大限度地减少对分布式事务的需求。
有关其他事务使用注意事项(如运行时间限制和 oplog 大小限制),另请参阅生产注意事项。
MongoDB 提供哪些隔离保证?
根据读关注,客户端可在写入操作持久化之前看到写入结果:要控制读取的数据是否可以回滚,客户端可以使用 readConcern
选项。
什么是无锁读取操作?
版本 5.0 中的新增功能。
如果当另一个操作在集合上具有独占 (X) 写锁时,无锁读取操作不会受到阻止,则该无锁读取操作可立即运行。
从 MongoDB 5.0 开始,当另一个操作在集合上持有独占 (X) 写锁时,以下读取操作不会受到阻止:
写入集合时,mapReduce
和 aggregate
持有意向排他 (IX) 锁。因此如果集合上已经持有排他 X 锁,mapReduce
和 aggregate
写操作将被阻塞。
有关信息,请参阅: