在MongoDB中实现乐观并发控制(乐观锁--悲观锁)

“并发控制”是避免在并发环境下某条记录被错误地覆盖。例如在一次“读取”、“修改”、“提交”的事务中,除非进行合理控制,否则可能其中某次提交的数据就遗失了。所谓“悲观”并发控制,则意味着在某次事务的“开始”和“提交”之间不会出现任何“读取”操作(即这条记录被锁定了),这自然不会有问题。而乐观并发控制,则保证的是在某次“读取”和“提交”之间没有进行任何“提交”操作,否则便会提交失败,于是当前事务便会重新从“读取”这个最早的步骤开始。此类概念(或者说并发处理方式)在许多地方都有体现,例如在普通的并发编程中,lock就近似于“悲观”并发控制,而“软件事务内存”则类似于“乐观”并发控制。

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

在MongoDB中的做法也没有太大区别,只是它的update语句并不会返回它所影响的记录数,于是我们必须额外进行一次查询,例如文档上所记载

> t.update({_id: 1, version: 3}}, {$set: {Content: "New Content", version: 4}});
> db.$cmd.findOne({getlasterror: 1});
{"err":, "updatedExisting": true, "n": 1 , "ok": 1} // it worked

> t.update({_id: 1, version: 3}}, {$set: {Content: "New Content", version: 4}}); 
> db.$cmd.findOne({getlasterror: 1});
{"err":, "updatedExisting": false, "n": 0 , "ok": 1} // did not work

我们可以在update语句后面跟上一句db.$cmd查询,如果它返回updatedExisting为true,则表示更新成功了。我一开始担心db.$cmd查询的结果是否准确:如果在update语句和db.$cmd查询之间,另外一个连接恰好也执行了一次update操作,那么db.$cmd返回的是哪次更新的结果?从后来从邮件列表中得知,db.$cmd查询是与连接相关,这便不会有问题了。不过值得注意的是,如果您使用的的驱动程序是“自动管理连接”的,则可能您在程序中发起的两次查询会使用两个不同的链接。不过我猜成熟的驱动应该都有办法解决这个问题,例如MongoDB的官方.NET驱动便可以要求直接返回db.$cmd查询的结果,或者在代码里显式“固定”某个链接。如今MongoDB的官方驱动已经十分完善,将MongoDB的功能体现地淋漓尽致,我也正在它的基础上更新EasyMongo(经过几个项目使用,感想不错),和之前的“民间驱动”相比省了不少心——顺便一提,官方驱动其实也借用了民间驱动的不少代码,即便它们之间的API有许多差异。

除此之外,您也可以使用基于runCommand的findAndModify命令进行更新,更新条件自然同样需要包括版本号。如果更新成功,那么findAndModify命令则会返回“更新前”的数据,否则则返回空文档。一般来说,MongoDB的驱动也已经包含了runCommand命令,甚至对findAndModify的直接支持(例如官方的.NET驱动)。

参考MongoDB权威指南58-61页

原文地址:http://blog.zhaojie.me/2011/02/optimistic-concurrency-control-in-mongodb.html

@老赵

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值