MongoDB面试指南之并发
并发
MongoDB允许多个客户机读取和写入相同的数据。为了确保一致性,它使用锁定和其他并发控制措施来防止多个客户端同时修改同一块数据。总之,这些机制保证了对单个文档的所有写操作要么全部发生,要么根本不发生,并且客户端永远不会看到不一致的数据视图。
MongoDB使用什么类型的锁?
MongoDB使用多粒度锁定[1],允许在全局、数据库或集合级别上锁定操作,并允许各个存储引擎在集合级别下实现自己的并发控制(例如,在WiredTiger的文档级别)。
MongoDB使用读写锁,允许并发的读写器共享对资源(如数据库或集合)的访问,但在MMAPv1中,只允许对单个写操作进行独占访问。
除了用于读操作的共享锁定模式和用于写操作的独占锁定模式外,意图共享(IS)和意图独占(IX)模式表示使用更细粒度的锁读写资源的意图。当锁定在某个粒度时,所有更高级别的锁都使用一个intent锁进行锁定。
例如,在为写入锁定集合时(使用模式X),必须在intent exclusive (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() 输出中, 锁模式的含义如下:
Lock Mode | Description |
R | 共享锁 Shared (S) lock |
W | 排它锁 Exclusive (X) lock |
r | 意向共享锁 Intent Shared (IS) lock |
w | 意向排它锁 Intent Exclusive (IX) lock |
在MongoDB中锁的粒度有多大
WiredTiger引擎
从3.0版本开始,MongoDB使用WiredTiger存储引擎。对于大多数读写操作,WiredTiger使用乐观并发控制。WiredTiger只在全局、数据库和collection集合级别使用意图锁。当存储引擎检测到两个操作之间的冲突时,其中一个操作将引发写冲突,从而导致MongoDB透明地重试该操作。
一些全局操作(通常是涉及多个数据库的短期操作)仍然需要全局“实例级”锁。其他一些操作,比如删除一个集合,仍然需要一个独占的数据库锁。
MMAPv1引擎
MMAPv1存储引擎在3.0发行版系列中使用了集合级锁,这是对早期版本的改进,在早期版本中,数据库锁是最细粒度的锁。第三方存储引擎可以使用集合级锁定,也可以实现它们自己的细粒度并发控制。
例如,如果在使用MMAPv1存储引擎的数据库中有6个集合,并且某个操作采用了集合级别的写锁,那么其他5个集合仍然可以用于读写操作。独占数据库锁使所有6个集合在持有锁的操作期间不可用。
如何查看mongod实例上的锁的状态
要报告锁的锁使用信息,请使用下列任何方法:
db.serverStatus()
db.currentOp()
mongotop
mongostat, and/or
MongoDB云管理器 or Ops Manager, an on-premise solution available in MongoDB Enterprise Advanced
具体地说,serverStatus输出中的locks文档或当前操作报告中的locks字段提供了对锁类型和mongod实例中锁争用数量的深入了解。
终止操作db.killOp().
读或写操作是否会产生锁
在某些情况下,读和写操作可能会产生锁。长时间运行的读和写操作,例如查询、更新和删除,在许多情况下都会产生。在写操作中,MongoDB操作还可以在单个文档修改之间产生锁,这些修改会影响多个文档,比如使用multi参数的update()。
对于支持文档级并发控制的存储引擎(如WiredTiger),在访问存储时不需要产生结果,因为意图锁(在全局、数据库和集合级别持有)不会阻塞其他读取器和写入器。但是,业务将定期产生下列结果:
对于支持文档级并发控制的存储引擎(如WiredTiger),在访问存储时不需要产生结果,因为意图锁(在全局、数据库和集合级别持有)不会阻塞其他读取器和写入器。但是,以下操作会产生锁:
为了避免长时间的存储事务,因为这些事务可能需要在内存中保存大量数据;
作为中断点,可以终止长时间运行的操作;
允许需要对集合进行独占访问的操作,如索引/集合删除和创建
MongoDB的MMAPv1存储引擎使用基于其访问模式的启发式方法,在执行读取之前预测数据是否可能在物理内存中。如果MongoDB预测数据不在物理内存中,那么当MongoDB将数据加载到内存中时,操作将产生锁。一旦数据在内存中可用,操作将重新获取锁来完成操作。
一些常见的客户端操作占用哪些锁
下表列出了一些操作和它们用于文档级锁定存储引擎的锁的类型
Operation | Database | Collection |
Issue a query | r (Intent Shared) | r (Intent Shared) |
Insert data | w (Intent Exclusive) | w (Intent Exclusive) |
Remove data | w (Intent Exclusive) | w (Intent Exclusive) |
Update data | w (Intent Exclusive) | w (Intent Exclusive) |
Perform Aggregation | r (Intent Shared) | r (Intent Shared) |
Create an index (Foreground) | W (Exclusive) | |
Create an index (Background) | w (Intent Exclusive) | w (Intent Exclusive) |
List collections | R (Shared) | |
Map-reduce | W (Exclusive) and R (Shared) | w (Intent Exclusive) and r (Intent Shared) |
哪些管理命令锁定数据库
某些管理命令可以在较长时间内独占锁定数据库。在一些部署中,对于大型数据库,您可以考虑将mongod实例脱机,这样客户端就不会受到影响。例如,如果一个mongod是一个复制集的一部分,那么在维护过程中,让mongod脱机并让集合服务的其他成员加载它。
下列管理操作需要在数据库级长时间使用独占锁:
Commands | Methods |
cloneCollectionAsCapped | |
compact | |
convertToCapped | |
copydb. This operation may lock all databases. See Does a MongoDB operation ever lock more than one database? | db.copyDatabase(). This operation may lock all databases. See Does a MongoDB operation ever lock more than one database?. |
create when creating a very large (i.e. many gigabytes) capped collection | db.createCollection() when creating a very large (i.e. many gigabytes) capped collection |
createIndexes for indexes without background set to true | db.collection.createIndex() and db.collection.createIndexes() |
reIndex | db.collection.reIndex() |
repairDatabase | db.repairDatabase() |
以下管理操作锁定数据库,但只锁定很短的时间:
Commands | Methods |
authenticate | db.auth() |
createUser | db.createUser() |
dropIndexes | db.collection.dropIndex() |
getLastError | db.getLastError() |
isMaster | db.isMaster() |
replSetGetStatus | rs.status() |
renameCollection | db.collection.renameCollection() |
serverStatus | db.serverStatus() |
MongoDB操作会锁定多个数据库吗?
下面的MongoDB操作锁定多个数据库:
db.copyDatabase() 必须立即锁定整个mongod实例。
db.repairDatabase() 获取一个全局写锁,并将阻塞其他操作,直到它完成
User authentication对于使用2.6用户凭证的部署,需要管理数据库上的读锁。对于使用2.4模式进行用户凭证的部署,身份验证将锁定管理数据库和用户正在访问的数据库
对复制集的主锁的所有写操作,包括接收写操作的数据库和短时间内的本地数据库。本地数据库的锁允许mongod写入主服务器的oplog,占操作总时间的一小部分
复制集成员状态转换采用全局exlusive lock。
分片如何影响并发性
分片通过在多个mongod实例上分布集合来提高并发性,允许shard服务器(即mongos进程)并发地执行任意数量的操作到各个下游mongod实例。
在分片集群中,锁应用于每个单独的分片,而不是整个集群;也就是说,每个mongod实例都独立于分片集群中的其他实例,并使用自己的锁。一个mongod实例上的操作不会阻塞其他任何实例上的操作。
当写入副本集时,锁的作用域应用于主副本集。
并发性如何影响复制集主节点?
在复制中,MongoDB不将写操作串行地应用于Secondary服务器。Secondary机构成批收集oplog条目,然后并行应用这些批。其次,在应用写操作时不允许读操作,并且按写操作在oplog中出现的顺序应用它们。
MongoDB支持事务吗
由于单个文档可以包含相关数据,这些数据可以跨关系模式中的单独父-子表进行建模,因此MongoDB的原子单文档操作已经提供了满足大多数应用程序的数据完整性需求的事务语义。可以在单个操作中写入一个或多个字段,包括对多个子文档和数组元素的更新。MongoDB提供的保证确保文档更新时完全隔离;任何错误都会导致操作回滚,以便客户端接收到的是一致的视图
从4.0版本开始,对于需要对多个文档进行更新或需要对多个文档进行读操作之间保持一致性的情况,MongoDB为副本集提供多文档事务,而针对切分集群的事务安排在MongoDB 4.2中。
要点
在大多数情况下,与单个文档写入相比,多文档事务会带来更大的性能成本,而且多文档事务的可用性不应该代替有效的模式设计。对于许多场景,非规范化数据模型(嵌入文档和数组)对于您的数据和用例来说仍然是最优的。也就是说,对于许多场景,适当地对数据建模将最小化对多文档事务的需求
MongoDB提供了什么样的隔离保证
根据读关注点,客户端可以在写操作持久之前看到写操作的结果。要控制读取的数据是否可以回滚,客户端可以使用readConcern选项。