更多关于MongoDB的技术分享请关注我的公众号:mongodb_side
根据2014-08-24官方文档快照翻译(v2.6.4)
翻译 shingo(6623662005@163.com)
MongoDB的锁在2.2版的时候做了重大更新,所以文中大量的更新说明标记的是2.2版本,2.4和2.6之后只做了少许算法上的优化和某些具体操作上细小的变化。 以下是原文
2.2版更新。
MongoDB使用了专门的锁机制来保证多客户端在单库上读写的一致性,也避免了多个应用在同一时间修改同一数据块时可能引发的问题。锁能确保针对单条数据的所有写操作要么全部执行要么都不执行。(译者注:最后一句话的意思是指写操作可能同时会伴有对journaling的写,或者对复制集主节点写时会伴有对oplog的写,这种成对出现的写通过锁来达到要么全写入要么全不写入)
相关资料 MongoDB2.2 并发和内部实现介绍
MongoDB使用的是什么类型的锁?
MongoDB使用多读单写锁[1]来支持并发的读操作,但是只允许单个写操作排它的持有这个锁。
当存在一个读锁时,多数读操作都能共享该锁。但是,当存在一个写锁时,只有一个写操作能排它的持有这个锁,不会和任何其它的读写操作共享。
锁明显“对写更偏爱”(译者注:原文为writergreedy,字面意思为写贪婪的),意思是说写比读拥有更高的优先级。当一个读操作和一个写操作同时在等待一个锁时,MongoDB会将锁分配给写操作。
[1] 你可以将“多读单写锁”看成是“多读锁”或者“共享独占锁”。更多信息可以查看维基百科上的多读单写锁。 |
MongoDB中的锁是什么粒度的?
2.2更新。
从2.2版开始,针对绝大多数读写操作,MongoDB实现了数据库级的锁。一些全局的操作,比如一个涉及多个数据库的短暂操作仍然需要一个全局的锁。在2.2版之前每个MongoDB实例都只有一个全局的锁。
例如,假设有6个数据库,其中某个库有一个数据级的写锁,其它5个库依然能提供读写服务。MongoDB还有一个全局的锁,当某个操作锁住它时,这6个库在这期间对于其它操作是不可用的。
怎样查看mongod实例的锁状态?
下面几个方法可以查看锁的使用信息:
-
db.serverStatus()
-
db.currentOp()
-
mongotop
-
mongostat
-
MongoDB管理服务(MMS)
(译者注:2013年7月31日,官方文档将MMS的描述从MongoDB Monitoring Service 改为了MongoDB Management Service,即由1.0初期的纯监控服务改为了现在的管理服务)
再具体一点,serverStatus输出章节中的locks 部分或者是 currentOp章节中的locks 部分,都提供了mongod实例中锁类型和锁竞争数的信息。
如果要终止某个操作,可以执行db.killOp()。
读或写操作会在某个执行时间让出锁吗?
在一些情况下,读写操作会让出锁。
长时间执行的读写操作,例如查询、更新、删除,在很多时候都会让出锁。MonogoDB使用了一个预测磁盘访问模式的自适应算法来决定锁的让出(例如page faults)。
(译者注:在MongoDB2.0的时候,操作让出锁主要是基于时间片以及等待锁的操作数;2.2之后才采用了自适应算法)
某些影响多个文档的写操作在陆续对单个文档做数据修改的时也会让出锁,比如带multi 参数的update()操作。
MongoDB通过预测磁盘访问模式可以预测出,在读取前,目标数据是否可能存在于物理内存中。如果MongoDB预测到数据不在内存中,这个操作会让出锁直到数据被加载到内存。一旦数据在内存中可用,这个操作会再次获取锁来完成工作。
(译者注:MongoDB操作数据时会先将数据加载到内存,然后进行操作,如果数据不在内存中通过放出锁来保证源操作本身不会长时间的持有锁)
2.6版更新: 在扫描索引时MongoDB不会让出锁,即使已经预测到索引不在内存中。
哪些操作会对数据库产生锁?
2.2版更新。
下面的表格列出了常见的数据库操作以及这些操作的锁类型。
操作 | 锁类型 |
发起一个查询 | 读锁 |
游标 执行getmore操作 | 读锁 |
insert数据 | 写锁 |
删除数据 | 写锁 |
更新数据 | 写锁 |
Map-reduce | 读锁和写锁,除非Map-reduce操作被声明为非原子性的。部分map-reduce任务能够并发的执行。 |
创建索引 | 创建索引操作默认在前台执行,会长时间锁住数据库。 |
db.eval() | 写锁。 db.eval()会阻塞其它基于JavaScript的操作。 |
eval | 写锁。如果将nolock声明为, eval操作不会持有写锁也不会向数据库写入数据。 |
aggregate() | 读锁 |
哪些管理类命令会产生锁?
某些管理类命令会排它的锁住数据库很长时间。在一些大规模集群的部署环境下,可能需要对 mongod实例做离线操作避免某些操作影响客户端的访问。例如,某个mongod实例是复制集的一个成员,可以将这个实例下线来做维护类操作,复制集中的其它成员继续提供服务。
下面这些管理类操作会长时间持有数据库的排它锁(写锁):
-
db.collection.ensureIndex(),未将background属性设置为true
-
reIndex
-
compact
-
db.repairDatabase()
-
db.createCollection(),当创建一个非常大的固定集合时
-
db.collection.validate()
-
db.copyDatabase(),这个操作会锁住所有的数据库。见下文 有没有什么操作会锁住一个以上的数据库?
下面的操作只会锁住数据库很短的时间:
-
db.collection.dropIndex()
-
db.getLastError()
-
db.isMaster()
-
rs.status()(即 replSetGetStatus)
-
db.serverStatus()
-
db.auth()
-
db.addUser()
有没有什么操作会锁住一个以上的数据库?
下面这些操作会锁住多个数据库:
-
db.copyDatabase()会立即锁住整个mongod实例。
-
db.repairDatabase() 会获得全局的写锁,阻塞其它操作直到它完成工作。
-
Journaling,这是一个内部操作,它会短暂的锁住所有数据库。所有数据库共享一个journal。
-
用户认证 部署环境使用2.6版新的system.user schema只会锁住admin库。而在部署中使用2.4版schema的不仅会锁住admin库,也会锁住目标库。
-
对复制集主节点的所有写操作不仅会锁住目标库,也会锁住local库很小一段时间。通过Local库上的锁, mongod进程可以往主节点的oplog 表写数据,以及一些认证操作,不过这些都只占整个写操作时间的很少一部分。
分片上的并发
分片通过在多个mongod实例上部署分布式集合来提高并发的性能,allowing 允许分片服务器(即 mongos进程)借助下游mongod 实例的集群优势执行任意数量的并发操作。
mongod实例之间是相互独立的,并且使用的是MongoDB多读单写锁。mongod实例上的操作不会阻塞其它实例。
复制集主节点上的并发
在复制集中,当往主节点上某个表写入时,MongoDB也会对主节点的oplog进行写入,这是local 库上一个特殊的表。因此,MongoDB会锁住目标表的库和local库。mongod进程会锁住这两个库来保证数据一致性,让这两个库的写操作要么全执行要么全不执行。
次级节点上的并发
在复制集中,MongoDB不允许往次级节点连续写入(译者注:这里所说的往次级节点写入是指MongoDB内部的写入,客户端是不能往次级点节写入的)。次级节点会分批的收集oplog信息,这些批次的回写是并行执行的。当在进行写操作时,次级节点上的数据是不可读的,回写操作会按照数据在oplog中的顺序执行。
MongoDB允许复制集次级节点上的几个写操作并行执行,分两个阶段:
-
第一个阶段,通过一个读锁,mongod要确保所有与写操作有关数据都存在于内存中。在这个阶段,其它客户端可以在这个节点上做查询操作。
-
一个使用写锁的线程池允许所有的写操作相互协调的成批写入。
MongoDB支持什么样的JavaScript并发操作?
2.4修改:MongoDB2.4增加了JavaScript V8引擎,允许多个JavaScript 操作同时执行。2.4版之前,单个mongod实例只允许同时执行一个JavaScript 操作。