MongoDB中的常见问题(二)——并发

 

并发

从3.0版本开始, MongoDB允许多个客户端读写相同的数据。为了保证一致性,MongoDB使用了锁以及其它并发控制策略来防止多个客户端同步修改相同片的数据。总的说来,这些机制保证了对单一文档的所有写操作要么就是完整的,要么就完全失败,而客户端永远不会看到数据的不一致状态。

MongoDB使用什么类型的锁?

MongoDB使用多粒度的锁,能够允许在全局/数据库/集合级别上的锁操作,并且允许单个的存储引擎实现它们自己在集合级别之下的并发控制。(例如,WiredTiger中的文档级别)

MongoDB使用读者-写者锁允许并发的读者共享资源读取,例如,对数据库或者集合的读取;但是在MMAPv1中,对单一的写操作提供了独占的读取。

除了读操作的共享(S)锁模型和写操作的独占(X)锁模型,意向共享(IS)和意向独占(IX)模式表示了一个使用更合适粒度锁对资源进行读写的意图。在设置了某个粒度的锁之后,所有更高的级别都会使用一个意图锁。

例如,当使用X模式锁对一个集合的读操作进行锁定时,对应的数据库锁和全局锁都会以一个独占的意图锁(IX)模式锁定。一个单一的数据库可以同步以IS和IX模式缩影,但是独占锁(X)不能与其它任何模式共存,而共享锁(S)只能与意向共享锁(IS)共存。

锁是公平的,会将读和写操作按顺序排列。然而,为了优化吞吐,当响应一个请求的时候,所有其它兼容的请求也会同时被响应,并且在响应一个冲突的请求之前俺释放锁。例如,考虑一个X锁刚被释放的情况,冲突队列包含下列请求:

  1. IS->IS->X->X->S->IS

在严格的先进先出顺序中,只会相应前两个IS模式。但是,MongoDB实际上将会响应所有的IS和S模式。一旦它们全部结束止呕,它将会相应X,即使新的IS和S模式同时也进入队列。由于经常会移动队列中其它所有的请求,不可能造成任何请求的饥饿现象。

MongoDB中的锁粒度为多少?

从3.0版本开始
对于WiredTiger
从3.0版本开始,MongoDB使用WiredTiger存储引擎。

对于大部分读写操作,WiredTiger使用乐观的并存控制。WiredTiger在全局/数据库/集合层面只使用意图锁。当存储引擎检测到两个操作之间的冲突时,其中一个将会发起一个写冲突,使得MongoDB透明地重新执行该操作。

一些全局操作,一般是涉及到多个数据库的短操作,依然会要求一个全局的“实例级别”的锁。而其它操作,例如:删除一个集合,也会要求一个独占的数据库锁。

对于MMAPv1
MMAPv1存储引擎使用3.0发布版本系列的集合级别的锁,提高了之前版本的数据库级别的锁。第三方的存储引擎肯恶搞会使用集合级别的锁或者实现它们自己的更佳级别的并发控制。

例如,如果我们在数据库中有6个集合使用MMAPv1存储引擎而一个操作获取了集合级别的锁没那么其它5个集合也可用于读写操作。一个独占锁将会在操作拥有该锁期间使得全部的6个集合都不可用。

如何查看mongod实例的锁状态?

我们可以使用下面方法中的任何一个来获取锁上面的利用信息:

  • db.serverStatus()
  • db.currentOp()
  • mongotop
  • mongostat

其中, db.serverStatus()输出中的锁文档,或者db.currentOp()中的locks字段中提供了我们mongod实例中锁的类型以及数量。

可以使用db.killOp()来终止一个操作。

一个读/写操作是否会让出锁?

在一些情况下,读写操作让出它们的锁。

在很多情况下,长时间允许的读写操作,例如查询/更新和删除等,会让出。MongoDB的操作在影响多个文档(例如:使用multi参数的update()操作)的写操作修改单个文档时也可能让出锁。

MongoDB的MMAPv1存储引擎在读操作之前会根据它的读取模式来预测诗句是否有可能在物理内存中俩使用启发式锁。如果MongoDB*预测*数据不在物理内存中,在MongoDB将数据载入内存时会让出它的锁。一旦数据在内存中可用,该操作将会重新获取锁来完成这个操作。

对于支持文档级别并发控制的存储引擎,例如WiredTiger,当存取引擎为全局/数据库和集合级别的意图锁时,让出不是必要的,它们并不会阻塞其它的读写操作。

从2.6版本开始,在扫描索引时,即使预测索引不在内存中,MongoDB也不会让出锁。

哪些操作会对数据库进行锁操作?

下表中列出了常用的数据库操作以及它们使用的锁类型:

操作 锁类型
执行一次查询 Read lock
从游标获取更多数据 Read lock
插入数据 Write lock
删除数据 Write lock
更新数据 Write lock
Map-reduce 除非操作声明为非原子操作,Read lock 和 write lock,部分map-reduce job可以并发执行
创建一个索引 在前台创建索引的话默认会锁定数据库一段时间
db.eval()从3.0版本开始过期 Write lock。db.eval()方法在执行JavaScript函数时会获取一个全局write lock。为了避免获取该全局锁,我们可以在使用eval命令时指定: nolock: true
eval从3.0版本开始过期 Write lock。默认地,eval命令在执行JavaScript函数时会获取一个全局write lock。为了避免获取该全局锁,我们可以在使用eval命令时指定: nolock: true。然而,JavaScript函数中的逻辑有可能会在写操作时持有Write locks。
aggregate() Read lock

什么管理员命令会锁定数据库?

一些管理员命令可能会独占地锁定数据库一段时间。在一些部署中,对于大型数据库,我们可以考虑将mongod实例下线以保证客户端不会受到影响。例如,如果mongod实例是复制集的一部分,可以在维护进行的过程中,让实例下线而让复制集服务的其它成员来进行服务。

下面的管理员命令会独占地锁定数据库一段时间:
– db.collection.createIndex(),在没有将background设置为true时;
– reIndex
– compact
– db.repairDatabase()
– db.createCollection(),在创建一个非常大的集合时(例如,许多GB)
– db.collection.validate()
– db.copyDatabase()。该操作可能会锁定所有数据库。

下面的管理员命令也会锁定数据库但是只会持有锁非常短的时间:
– db.collection.dropIndex()
– db.getLastError()
– db.isMaster()
– rs.status() (i.e. replSetGetStatus)
– db.serverStatus()
– db.auth()
– db.addUser()

MongoDB操作是否会锁定超过一个数据库?

下面的MongoDB操作可能会锁定多个数据库:
– db.copyDatabase()必须立刻锁定整个mongod实例
– db.repairDataBase()获取一个全局的Write lock,并且会阻塞其它操作,直到它完成
– 日志,是一个内部操作,会在非常短的周期内锁定所有数据库。所有数据库共享同一个日志
– 用户授权

打赏
微信扫一扫支付
微信logo微信扫一扫, 打赏作者吧~

mickey

记录生活,写给几十年后的自己。