Hello World
Spiga

在MongoDB中实现乐观并发控制

2011-02-22 09:39 by 老赵, 12993 visits

说起来,自从接触了MongoDB以后,我在大小项目中就再也没有接触过关系型数据库了。性能倒不是什么主要问题,主要是方便,例如我可以在MongoDB中直接保存数组,然后把其中的元素当作查询条件,而在关系型数据库中,则需要使用额外的表格,然后再JOIN等等。当然,在MongoDB中很难进行JOIN,于是对于某些场景下会略显麻烦,但在记忆中我似乎真没什么束手束脚的情况。这方面我还没有仔细分析,可能MongoDB支持保存复杂对象会有所帮助吧。以上都是废话,这里我简单谈一下如何在MongoDB中实现乐观并发控制。当然加入您对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的功能,本文内容其实是十分显而易见的,的确就是这么简单。

最后顺手发布两条“招聘”消息(地点都是上海):

  1. 钢琴入门老师(1名):指导我进行钢琴基础技术训练及乐理(如24个大小调音阶,快速识谱方法)。本人小时候学过点钢琴,但基础没有打好,现在打算认真来过。
  2. 优秀.NET程序员(1~2名):加入盛大创新院工作,要求“自认为对.NET有较好了解”,有一定编程基础,热爱技术学习。热情参与社区活动者为佳。

欢迎各位推荐或自荐,联系方式(邮件):

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

37 条回复

  1. 四有青年
    203.156.244.*
    链接

    四有青年 2011-02-22 10:50:18

    左手coding,右手弹钢琴,惬意啊

  2. Belleve Invis
    218.22.21.*
    链接

    Belleve Invis 2011-02-22 13:14:48

    左手搂着妹子右手coding wwwwwwwwwwwwwwwwwww

  3. FMax
    183.62.146.*
    链接

    FMax 2011-02-22 16:57:04

    MongoDB现在居然有官方的.net驱动了?看来得找个时间把现在替换掉了。

  4. mathgl
    220.173.35.*
    链接

    mathgl 2011-02-22 18:48:44

    老赵的mongo是在64 bit下跑的吗??

    32 bit总有 2g的限制比较麻烦

  5. matrixdom
    222.212.218.*
    链接

    matrixdom 2011-02-22 21:27:58

    很久没来看技术文章了,路过

  6. 倪大虾
    58.37.224.*
    链接

    倪大虾 2011-02-22 21:33:45

    报个名,怎么样!!!

  7. 老赵
    admin
    链接

    老赵 2011-02-22 23:33:45

    @mathgl

    现在做正式点的项目哪儿还会用32位的机器啊,呵呵。

  8. 链接

    杨俊明 2011-02-23 09:09:22

    其实蛮想去盛大跟牛人们一起工作的,只是觉得技术水平还不够,有点信心不足。

  9. halida
    58.39.246.*
    链接

    halida 2011-02-23 17:55:00

    哈, 我今天刚好看到findAndModify, 才把mongodb更新到1.6上面去. 用mongodb有什么体会以及最佳实践可以分享的? 我们估计也要上mongodb了, 不知道要具体注意什么.

  10. afk
    123.127.212.*
    链接

    afk 2011-02-25 17:19:27

    EasyMongo有点啥文档或者入门之类的没?

  11. 链接

    ZHOU 2011-02-27 16:11:59

    你的blog的search功能好像挂了

  12. 老赵
    admin
    链接

    老赵 2011-02-28 01:06:46

    @ZHOU

    其实是从来没好过……

  13. 链接

    zhixin 2011-02-28 10:06:46

    @老赵: 现在做正式点的项目哪儿还会用32位的机器啊,呵呵。

    看来我还真没做过正式点的项目

  14. wgz
    60.191.94.*
    链接

    wgz 2011-02-28 13:43:17

    能例举几个面试问题吗?想了解下对优秀程序员的定义

  15. 老赵
    admin
    链接

    老赵 2011-02-28 18:53:32

    @wgz

    我想想啊。

  16. aisk
    221.221.8.*
    链接

    aisk 2011-03-02 15:08:51

    搜索功能直接用谷歌站内搜索吧

  17. 老赵
    admin
    链接

    老赵 2011-03-02 16:18:55

    @aisk

    本来就是这个打算,呵呵。

  18. 龙二
    125.39.132.*
    链接

    龙二 2011-03-04 00:10:29

    赵老师 可否写一篇怎么使用EasyMongo的文章 呵呵

  19. 小冰雹
    131.107.0.*
    链接

    小冰雹 2011-03-04 08:15:55

    我在程序员里算钢琴弹得好的,在教钢琴的里算程序写得棒的……这样行不O(∩_∩)O

  20. 链接

    leeolevis 2011-03-05 01:39:41

    老赵你好,我在用你的EasyMongo框架操作一个动态查询的列表时遇到了些疑问和问题想请教一下

    一、EasyMongo暂不支持ESQL语句吧?

    二、我在参考博客园上一个朋友的LINQ动态排序时碰到了Boolean Like(System.String, System.String) is not supported错误,原因是ExpressionVisitor里没有Like的Method.Name,是框架不支持吗,对框架不熟,过来求助一下:)

  21. 老赵
    admin
    链接

    老赵 2011-03-05 09:42:24

    @leeolevis

    其实EasyMongo支持的是MongoDB,所以考虑的不是SQL更不是ESQL了,它也跟LINQ to SQL没什么关系。还有就是你说的不支持的场景,能给段实际的代码吗?

  22. leeolevis
    125.39.67.*
    链接

    leeolevis 2011-03-05 11:48:24

    明白:)框架很好,还在熟悉和学习中。动态构造查询条件是参考的这篇文章,OrderBy是用的自己的扩展方法。

    protected Expression<Func<Agencie, bool>> GetPredicateExpression()
    {
        var query = QueryBuilder.Create<Agencie>()
            .Like(c => c.Region, txtRegion.Text.Trim())
            .Like(c => c.Name, txtName.Text.Trim())
            .Like(c => c.Levels, txtLevels.Text.Trim())
            .Like(c => c.Nature, txtNature.Text.Trim())
            .Like(c => c.Phone, txtPhone.Text.Trim())
            .Like(c => c.Address, txtAddress.Text.Trim());
        return query.Expression;
    }
    
    //利用构造的表达式进行分页
    public List<Agencie> SortMS_AgenciesByQueryCondition(Expression<Func<Agencie, bool>> predicateExpression, string sortField, string sortWay, int pageIndex, int pageSize)
    {
        var queryList = Entities.Where(predicateExpression).OrderBy("" + sortField + " " + sortWay + "").Skip(pageIndex).Take(pageSize).ToList();
        return queryList;
    }
    
  23. 老赵
    admin
    链接

    老赵 2011-03-05 12:07:44

    @leeolevis

    EasyMongo不是LINQ to SQL,不同LINQ Provider是不一样的,就好比EasyMongo没有Like一样。MongoDB没有like功能,EasyMongo肯定提供不了的。

    不过我猜你可以用MongoDB的正则表达式功能来做差不多的事情,比如在EasyMongo里面:

    articles.Where(a => a.Title.Matches("abc", ""));
    

    这好比在SQL里面:

    SELECT * FROM Article WHERE Title LIKE "%abc%";
    
  24. 链接

    leeolevis 2011-03-05 12:25:11

    明白了,怪我没来得及看文档,多谢老赵了。

  25. afk
    114.246.216.*
    链接

    afk 2011-03-21 21:27:55

    问题求解。在easymongo里,用Property做映射时候。如果这里面映射的类型是个自定义类型该咋办呢。有没有类似NHibernate里的Component,还是必须的自己写个ITypeProcessor才能搞定?

  26. 老赵
    admin
    链接

    老赵 2011-03-21 22:02:11

    @afk

    对,就是写个ITypeProcessor。

  27. Jorise
    180.168.78.*
    链接

    Jorise 2011-04-13 11:38:56

    老赵找钢琴老师?是要专业老师?还是~~~~~~ 我是纯业余自学~~~ 木吉他9年,键盘3年~~~~~~~~ 乐理也懂点~~~可以交流交流。

  28. Jorise
    180.168.78.*
    链接

    Jorise 2011-04-13 11:39:42

    忘说了,香楠男~~

  29. live41
    116.23.87.*
    链接

    live41 2011-04-21 15:54:35

    自己写mapreduce分片处理

  30. live41
    116.23.87.*
    链接

    live41 2011-04-21 15:55:27

    mathgl 2011-02-22 10:48:44 老赵的mongo是在64 bit下跑的吗??

    32 bit总有 2g的限制比较麻烦

    自己写mapreduce分片处理

  31. pan
    115.236.66.*
    链接

    pan 2011-08-22 17:28:48

    老赵你好,我还没用过你的EasyMongoDB,不过还是想问一下,你的封装对MongoDB的数据分布有没有简化。

  32. 老赵
    admin
    链接

    老赵 2011-08-23 23:24:48

    @pan

    没有做过这方面事情,不过我估计你可能也搞错了点东西……

  33. daydayfree
    219.239.6.*
    链接

    daydayfree 2011-09-13 17:20:19

    老赵同学: 您说的是不是每次做更新操作时候,首先读出version字段中的值,然后更新时添加条件where version = :version, 然后再读取一次判断是否更新成功,总共3次操作数据库; 如果面临的是多条更新,批量的,这样效率是不是就变的很低了?

  34. 魏晋遗疯
    14.153.238.*
    链接

    魏晋遗疯 2011-12-04 23:10:01

    老赵,请教一个问题: 我使用官方C# Driver

    [Serializable]
    public class ActionClass
    {
        [BsonId(IdGenerator = typeof(StringObjectIdGenerator))]
        public string ID { get; set; }
        public string Content { get; set; }
    
        public string[] Tag { get; set; }
    }
    

    Inser方式为:

    public static string Insert(ActionClass row)
    {
        SafeModeResult rtn = DB.GetCollection(TableName).Insert<ActionClass>(row);
        return row.ID;
    }
    

    插入以后变成:

    _id : 4edb82e2089a001064402fb2  
    Content : 现在才v  
    Tag : **System.Collections.Generic.List`1[System.String]**
    

    这里应该是["A","B","C"]才对的,但为什么序列化成类型了呢?

  35. 小小鸟
    218.17.162.*
    链接

    小小鸟 2011-12-19 11:12:41

    老赵能不能讲解一下麦库记事本的框架。

    我看里面有sqlite;web service;还经常的在日志文件看到如下内容: E:\projects\mongodb-mongo-csharp-driver-6b38c36\Bson\IO\BsonBuffer.cs:行号 217\r\n 在 MongoDB.Bson.IO.BsonBuffer.LoadFrom(Stream stream) 位置 E:\projects\mongodb-mongo-csharp-driver-6b38c36\Bson\IO\BsonBuffer.cs:行号 195\r\n 在 MongoDB.Driver.Internal.MongoConnection.ReceiveMessageTDocument 位置 E:\projects\mongodb-mongo-csharp-driver-6b38c36\Driver\Internal\MongoConnection.cs:行号 356\r\n 在 MongoDB.Driver.Internal.MongoCursorEnumerator`1.GetReply(MongoRequestMessage message) 位置 。。。

  36. weijihe
    222.248.233.*
    链接

    weijihe 2012-12-11 23:59:52

    请问mongodb的16MB限制意味着单个文档不可以超过,比如有个集合存储微博用户,那么用户A对应的好友做内嵌数组,假设很多,超过这个限制会出现什么情况?该如何处理谢谢

  37. zhkzyth
    119.129.217.*
    链接

    zhkzyth 2013-11-18 16:51:25

    前輩,演示裡面的代碼好像有誤.....比如括號配對問題....好吧,我挖墳了=.=

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我