nosql数据库(MongoDB)并发控制

本文探讨了MongoDB中乐观锁的并发控制机制,详细介绍了如何利用版本字段实现乐观锁,以及在Java环境下使用findAndModify命令进行原子更新的方法。

MongoDB在我们的生产环境中已经大规模的使用,它的性能与稳定已经得到的充分的验证,稳定在线的时间已经有一年多了。在这个过程中的确给我们带来了很多性能上的优势,虽然它不像关系型数据那样有方便的join查询,但就目前我们的应用场景这些缺点(暂且把它当做缺点吧)都是可以接受的。最近在思考了下nosql数据库并发控制方面的问题,在此记录一下。

数据库的并发控制机制不外乎就两种情况:

1.悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。

2.乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性,乐观锁不能解决脏读和多读的问题。

悲观锁假定其他用户企图访问或者改变你正在访问、更改的对象的概率是很高的,因此在悲观锁的环境中,在你开始改变此对象之前就将该对象锁住,并且直到你提交了所作的更改之后才释放锁。悲观的缺陷是不论是页锁还是行锁,加锁的时间可能会很长,这样可能会长时间的限制其他用户的访问,也就是说悲观锁的并发访问性不好。

乐观锁则认为其他用户企图改变你正在更改的对象的概率是很小的,因此乐观锁直到你准备提交所作的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短,乐观锁可以用较大的锁粒度获得较好的并发访问性能。但是如果第二个用户恰好在第一个用户提交更改之前读取了该对象,那么当他完成了自己的更改进行提交时,数据库就会发现该对象已经变化了,这样,第二个用户不得不重新读取该对象并作出更改。这说明在乐观锁环境中,会增加并发用户读取对象的次数。

 

MongoDB不支持事务,也就没有数据库级别的悲观锁了。那么我们的并发控制只能依赖乐观锁,乐观锁非常适合我的应用场景,并且性能更高。刚才说的是数据库级别的并发控制,当然如果说到程序级别并发控制机制,同样是悲观锁和乐观锁。我们的经常用的lock就是一种悲观锁,不论是java还是.net。那乐观锁呢?当然这个就是软件事务内存ClojureScala言语的内存管理,它们天生就是支持并发编程的。

 如果要在普通的关系型数据库里实现乐观并发控制,我们一般需要为其加上一个额外的Version字段,它是整型,也可能是个时间戳。在更新某条记录时,我们将这个字段的旧值作为UPDATE语句的条件之一,同时这个字段也会写入新的值。如果这次更新影响了某条记录,那么表示更新成功,反之则表示这条记录已经被删除,或是在读取提交之间遇到了其他提交操作。在SQL Server中存在一个Timestamp类型,这个类型的字段会在记录修改时自动更新。

要在MongoDB实现乐观锁,方式差不多,只是update完了之后,不会返回修改的数据条数,得还要自己去查询一下是否修改成功。

>db.log.update({"uuid":"1",version:1},{$set:{"enddate":"2012-5-19 17:17:10",version:2}})

> db.$cmd.findOne({getlasterror:1}) {   "updatedExisting" : true,     "n" : 1,        "connectionId" : 1, "err" : null,        "ok" : 1 }

>db.log.update({"uuid":"1",version:1},{$set:{"enddate":"2012-5-19 17:17:10",version:2}})

> db.$cmd.findOne({getlasterror:1}) {   "updatedExisting" : false,     "n" : 0,        "connectionId" : 1, "err" : null,        "ok" : 1 }   

  

update语句后面跟上一句db.$cmd查询,如果它返回updatedExistingtrue,则表示更新成功了。当然如果使用java驱动的话,可以使用dbCollection.updatedbCollection.getStats(),便可以更新并且返回状态信息。但是db.$cmd查询的结果是否准确呢?如果在update语句和db.$cmd查询之间,如果另外一个连接恰好也执行了一次update操作,那么db.$cmd返回的是哪次更新的结果?通过查询官方资料,db.$cmd查询是与连接相关,这便不会有问题了。不过值得注意的是,驱动程序是自动管理连接的,也就是说当update()完成之后getstats()有可能使用的不是同一个链接了,这个时候db.$cmd返回的状态信息就不准确。所以如果采用上诉方式你要确保自己两次获得的是同一个链接。如果你想一直使用同一个连接,可以用下边这种方式:

DB db...;

db.requestStart();

code....

db.requestDone();

但是如果最后db.requestDone()没有被调用,该连接不会被交还给线程池,所以,一定要在finally块中调用db.requestDone()。

 

 由于我使用spring来管理mongo驱动,我不喜欢上面那种保持一个链接的方式,所以我用findAndModify来更新数据,当然更新条件自然同样需要包括版本号。如果更新成功,那么findAndModify命令则会返回更新前的数据,否则则返回空文档。这种使用数据库命令行的方式就可以避免保持同一个链接的要求。该方式也是我推荐的方式,官方文档已经说的很清楚了。

This command can be used to atomically modify a document (at most one) and return it. Note that, by default, the document returned will not include the modifications made on the update.

以上便是mongodb并发控制乐观锁的实现方式。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值