【问题标题】:CouchDB/Couchbase/MongoDB transaction emulation?CouchDB/Couchbase/MongoDB 事务仿真?
【发布时间】:2014-09-09 02:43:49
【问题描述】:

我以前从未使用过 CouchDB/MongoDB/Couchbase,并且正在为我的应用程序评估它们。一般来说,它们似乎是我想使用的一种非常有趣的技术。但是,来自 RDBMS 背景,我对缺乏事务感到困惑。但同时,我知道,考虑到数据的组织方式,对事务的需求将大大减少,就像在 RDBMS 中那样。

话虽如此,我有以下要求,但不确定是否/如何使用 NoSQL DB。

  1. 我有一份客户名单
  2. 每个客户端可以有多个文件
  3. 每个文件都必须按特定客户端的顺序编号

给定一个 RDBMS,这将相当简单。一张表用于客户端,一张(或多张)用于文件。在客户表中,保留最后一个文件号的计数器,并在向文件表中插入新记录时加一。将所有内容包装在事务中,您可以确信存在不一致之处。哎呀,为了安全起见,我什至可以在 (clientId, filenumber) 索引上设置唯一约束,以确保不会有相同的文件编号用于客户端两次。

如何在 MongoDB 或 CouchDB/base 中完成类似的任务?它甚至可行吗?我一直在阅读有关两阶段提交的信息,但我似乎无法理解在这种情况下它是如何工作的。 Spring/Java 中是否有任何东西可以提供可与这些 DB 一起使用的两阶段提交,还是需要自定义代码?

【问题讨论】:

    标签: java spring mongodb transactions couchdb


    【解决方案1】:

    Couchdb 默认是事务性的。 couchdb 中的每个文档都包含一个_rev 键。对文档的所有更新都是针对此 _rev 键执行的:-

    1. 获取文档。
    2. 使用 _rev 属性发送它以进行更新。
    3. 如果更新成功,则说明您已更新文档的最新_rev
    4. 如果更新失败,则该文档不是最新的。重复步骤 1-3。

    查看this answer by MrKurt了解更详细的说明。

    couchdb recipies 有一个银行示例,展示了如何在 couchdb 中完成交易。

    还有这篇 atomic bank transfers 文章说明了 couchdb 中的事务。

    无论如何,所有这些链接的共同主题是,如果您遵循 couchdb 模式来更新 _rev,您的数据库中就不会出现不一致的状态。

    哎呀,为了安全起见,我什至可以对 (clientId, filenumber) 索引设置一个唯一约束,以确保不会有相同的文件号用于客户端两次。

    所有 couchdb 文档都是唯一的,因为两个文档中的 _id 字段不能相同。查看view cookbook

    这很简单:在 CouchDB 数据库中,每个文档都必须有一个唯一的 _id 字段。如果您需要数据库中的唯一值,只需将它们分配给文档的 _id 字段,CouchDB 就会为您强制执行唯一性。

    但有一个警告:在分布式情况下,当您运行多个接受写入请求的 CouchDB 节点时,只能保证每个节点或 CouchDB 外部的唯一性。 CouchDB 将允许将两个相同的 ID 写入两个不同的节点。在复制时,CouchDB 将检测到冲突并相应地标记文档。

    根据评论编辑

    如果您想根据另一个文档的成功插入来增加一个文档中的字段

    在这种情况下,您可以使用单独的文档。您插入一个文档,等待成功响应。然后添加另一个文档,如

    {_id:'some_id','count':1}

    有了这个,您可以设置一个 map reduce 视图来简单地计算这些文档的结果,并且您有一个更新计数器。您所做的只是插入一个新文档以反映插入成功,而不是更新单个文档以进行更新。

    我总是遇到这样的情况,即文件插入失败会使数据库处于不一致状态,尤其是在另一个客户端同时成功插入文件的情况下。

    好的,我已经描述了如何对单独的文档进行更新,但即使在更新单个文档时,您也可以避免不一致:

    1. 插入新文件
    2. 当 couchdb 给出成功消息时 -> 尝试更新计数器。

    为什么会这样?

    这是因为当您尝试更新update document 时,您必须提供_rev 字符串。您可以将_rev 视为您文档的本地状态。考虑这种情况:-

    1. 您已阅读要更新的文档。
    2. 您更改了一些字段。
    3. 同时另一个请求已经更改了原始文档。这意味着该文档现在有一个新的_rev
    4. 但是您请求 couchdb 使用您在第 1 步中读取的_revstale 更新文档。
    5. Couchdb 会产生异常。
    6. 您再次阅读该文档,获取最新的_rev 并尝试更新它。

    因此,如果您这样做,您将始终需要根据文档的最新版本进行更新。我希望这能让事情变得更清楚一些。

    注意:

    正如 Daniel 所指出的,_rev 规则不适用于批量更新。

    【讨论】:

    • "Couchdb 默认是事务性的。" - 只是澄清......这是每个文件。不跨越多个文档。如果验证处理程序无效,您可以将批量更新视为“一个”单元,但它不是事务。
    • 我之前读过原子银行转账文章,虽然使用了,但它们不处理需要保留准确计数的情况。如果您想根据另一个文档的成功插入来增加一个文档中的字段,我无法看到银行转账示例是如何工作的。无论我如何尝试构建它,我总是会遇到这样的情况:文件插入失败会使数据库处于不一致的状态——尤其是在另一个客户端同时成功插入文件的情况下。
    【解决方案2】:

    是的,您可以使用适当的方法对 MongoDB 和 Couchbase/CouchDB 执行相同的操作。

    首先在 MongoDB 中你有唯一索引,这将帮助你确保部分问题: - http://docs.mongodb.org/manual/tutorial/create-a-unique-index/

    您还有一些模式可以正确实现序列: - http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/

    您有很多选择来实现跨文档/集合事务,您可以在此博客文章中找到一些关于此的有用信息: http://edgystuff.tumblr.com/post/93523827905/how-to-implement-robust-and-scalable-transactions(这里详细记录了 2 阶段提交:http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/

    既然你在谈论 Couchbase,你也可以在这里找到一些模式: http://docs.couchbase.com/couchbase-devguide-2.5/#providing-transactional-logic

    【讨论】:

    • 我已经阅读了关于两阶段提交的文章,但我仍然不知道它会如何应用在这里。如果我有一个线程,我可以看到它工作,但如果我有多个线程,我不能保证原子性 - 即线程 #2 可以在线程 #1 完成之前完全执行,如果第一个失败,但是第二次成功,文件号/计数器将不正确。除非我误解了如何正确地进行两阶段提交(我不这么认为)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-27
    • 2017-03-01
    • 2013-09-20
    • 2011-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多