代码之家  ›  专栏  ›  技术社区  ›  user2427

我可以在CouchDB中执行事务和锁吗?

  •  78
  • user2427  · 技术社区  · 16 年前

    我需要做事务(开始、提交或回滚)、锁(选择更新)。 如何在文档模型数据库中执行此操作?

    编辑:

    情况是这样的:

    • 我想经营一个拍卖网站。
    • 我也在想如何直接购买。
    • 在直接采购中,我必须减少项目记录中的数量字段,但前提是数量大于零。这就是为什么我需要锁和事务。
    • 我不知道如何在没有锁和/或事务的情况下解决这个问题。

    我能用CouchDB解决这个问题吗?

    7 回复  |  直到 8 年前
        1
  •  135
  •   Luccas    10 年前

    不。CouchDB使用“乐观并发”模型。最简单的说法是,这意味着您将随更新一起发送文档版本,如果当前文档版本与您发送的不匹配,CouchDB将拒绝更改。

    这真的很简单。您可以为couchdb重新构造许多基于事务的正常场景。不过,在学习couchdb时,您确实需要抛开RDBMS领域的知识。它有助于从更高的层次处理问题,而不是试图将coach建模为基于SQL的世界。

    跟踪库存

    您概述的问题主要是库存问题。如果您有一个描述项目的文档,并且其中包含一个“可用数量”字段,那么您可以处理如下并发问题:

    1. 检索文档,注意 _rev CouchDB发送的属性
    2. 如果数量字段大于零,则递减该字段
    3. 使用 女牧师 财产
    4. 如果 女牧师 匹配当前存储的号码,完成!
    5. 如果发生冲突(当 女牧师 不匹配),检索最新文档版本

    在这个例子中,有两种可能的失败场景需要考虑。如果最新的文档版本的数量为0,您将像在RDBMS中那样处理它,并警告用户他们实际上无法购买他们想要购买的东西。如果最新的文档版本的数量大于0,则只需使用更新的数据重复该操作,然后从头开始。这迫使您比RDBMS做更多的工作,并且如果有频繁的、冲突的更新,可能会变得有点烦人。

    现在,我刚才给出的答案假设您将在CouchDB中以与在RDBMS中相同的方式进行操作。我可能会用不同的方式来处理这个问题:

    我将从包含所有描述符数据(名称、图片、描述、价格等)的“主产品”文档开始。然后,我将为每个特定实例添加一个“库存票据”文档,其中包含 product_key claimed_by . 如果你卖的是一个锤子模型,并且有20个要卖,你可能会有带钥匙的文件,比如 hammer-1 , hammer-2 等等,代表每个可用的锤子。

    然后,我将创建一个视图,它为我提供一个可用锤子的列表,并使用reduce函数让我看到一个“total”。这些都是完全现成的,但应该给你一个什么样的工作视图的想法。

    地图

    function(doc) 
    { 
        if (doc.type == 'inventory_ticket' && doc.claimed_by == null ) { 
            emit(doc.product_key, { 'inventory_ticket' :doc.id, '_rev' : doc._rev }); 
        } 
    }
    

    这会按产品密钥给我一个可用的“票”列表。当有人想要购买锤子时,我可以获取一组这些信息,然后迭代发送更新(使用 id 女牧师 )直到我成功地申领一张(以前申领的票将导致更新错误)。

    减少

    function (keys, values, combine) {
        return values.length;
    }
    

    这个reduce函数只返回无人认领的总数 inventory_ticket 物品,这样你就可以知道有多少“锤子”可供购买。

    告诫

    这个解决方案代表了对您所提出的特定问题的大约3.5分钟的总体思考。有更好的方法可以做到这一点!也就是说,它确实大大减少了相互冲突的更新,并减少了用新的更新来响应冲突的需要。在此模型下,您将不会有多个用户试图更改主产品条目中的数据。最坏的情况是,您会有多个用户试图申领一张罚单,如果您从视图中抓取了其中的几张,您只需继续下一张罚单,然后再试一次。

    参考文献: https://wiki.apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB.3F

        2
  •  24
  •   Kerr    16 年前

    扩大库尔特先生的回答。在很多情况下,您不需要按顺序兑换股票。您可以从剩余的票中随机选择,而不是选择第一张票。考虑到大量的票和大量的并发请求,与所有试图获得第一张票的人相比,您将大大减少对这些票的争用。

        3
  •  20
  •   community wiki 3 revs, 2 users 97% ordnungswidrig    9 年前

    重新填充事务的设计模式是在系统中创建一个“张力”。对于银行账户交易的常用示例用例,您必须确保更新两个相关账户的合计:

    • 创建交易凭证“将10美元从账户11223转账到账户88733”。这会在系统中产生张力。
    • 解决所有交易文件的张力扫描,以及
      • 如果源帐户未更新,则更新源帐户(-10美元)
      • 如果源帐户已更新,但事务文档未显示此信息,则更新事务文档(例如,在文档中设置标记“sourcedone”)。
      • 如果目标账户未更新,则更新目标账户(+10美元)
      • 如果目标帐户已更新,但交易凭证未显示此信息,则更新交易凭证
      • 如果两个帐户都已更新,您可以删除交易记录文档或将其保留以供审核。

    对所有“张力文档”的张力扫描应在后端流程中完成,以缩短系统中的张力时间。在上面的示例中,当第一个帐户已更新但第二个帐户尚未更新时,会出现短期的预期不一致。如果您的couchdb是分布式的,那么必须以处理最终一致性的相同方式考虑这一点。

    另一种可能的实现完全避免了事务的需要:只需存储张力文档,并通过评估每个涉及的张力文档来评估系统的状态。在上面的示例中,这意味着一个帐户的合计仅确定为涉及该帐户的交易文档中的合计值。在couchdb中,您可以很好地将其建模为一个map/reduce视图。

        4
  •  5
  •   Dobes Vandermeer Tahbaza    13 年前

    不,CouchDB通常不适合事务性应用程序,因为它不支持集群/复制环境中的原子操作。

    CouchDB牺牲了事务处理能力以支持可扩展性。为了进行原子操作,您需要一个中央协调系统,这限制了您的可伸缩性。

    如果您可以保证只有一个couchdb实例,或者每个修改特定文档的人都连接到同一个couchdb实例,那么您可以使用冲突检测系统使用上面描述的方法创建一种原子性,但是如果您稍后扩展到集群或使用像cloudant这样的托管服务,它将分解你必须重做系统的那部分。

    所以,我的建议是用couchdb以外的东西来做你的账户余额,这样做会容易得多。

        5
  •  5
  •   wallacer    11 年前

    作为对手术问题的回应,沙发可能不是这里最好的选择。使用视图是跟踪库存的一个很好的方法,但是钳制到0或多或少是不可能的。当你阅读一个视图的结果,决定你可以使用一个“hammer-1”项目,然后写一个文档来使用它时,问题就在于你的比赛条件。问题是,如果视图的结果是存在>0 Hammer-1's,则没有原子方法只编写使用Hammer的文档。如果100个用户同时查询视图并看到1 Hammer-1,则他们都可以编写使用Hammer 1的文档,从而得到-99 Hammer-1's。在实践中,竞争条件将相当小-reall如果数据库运行的是localhost,则为y small。但是,一旦您进行了扩展,并且拥有了一个非站点的DB服务器或集群,这个问题将变得更加明显。无论如何,在一个与金钱相关的关键系统中,有这样一种竞争条件是不可接受的。

    对mrkurt响应的更新(可能只是更新了日期,或者他可能不知道一些couchdb特性)

    视图是处理CouchDB中余额/库存等问题的好方法。

    您不需要在视图中发出docid和rev。当您检索视图结果时,这两种方法都是免费的。发出它们——尤其是像字典这样的冗长格式——只会让视图增长到不必要的大。

    跟踪库存余额的简单视图应该更像这样(也在我的头脑中)

    function( doc )
    {
        if( doc.InventoryChange != undefined ) {
            for( product_key in doc.InventoryChange ) {
                emit( product_key, 1 );
            }
        }
    }
    

    约化函数更简单

    _sum
    

    这使用了 built in reduce function 它只是用匹配键对所有行的值求和。

    在这个视图中,任何文档都可以有一个成员“inventory change”,它将产品密钥映射到它们总库存的更改。IE.

    {
        "_id": "abc123",
        "InventoryChange": {
             "hammer_1234": 10,
             "saw_4321": 25
         }
    }
    

    将添加10个锤子1234和25个锯子4321。

    {
        "_id": "def456",
        "InventoryChange": {
            "hammer_1234": -5
        }
    }
    

    会烧掉库存中的5个锤子。

    使用这个模型,您永远不会更新任何数据,只会追加数据。这意味着没有更新冲突的机会。更新数据的所有事务性问题都消失了:)

    这个模型的另一个优点是数据库中的任何文档都可以从库存中添加和减去项目。这些文档中可以包含各种其他数据。您可能有一个“发货”文档,其中包含一系列有关接收日期和时间、仓库、接收员工等的数据,只要该文档定义了库存更改,它就会更新库存。就像一份“销售”文件和一份“损坏物品”文件等一样,在查看每一份文件时,它们读得非常清楚。视图处理所有的工作。

        6
  •  3
  •   Evan    16 年前

    实际上,你可以在某种程度上。看看 HTTP Document API 向下滚动到标题“用一个请求修改多个文档”。

    基本上,您可以在一个POST请求中创建/更新/删除一组文档 uri/dbname/_批量文件 他们要么成功要么失败。不过,该文件确实警告说,这种行为将来可能会改变。

    编辑:正如预测的那样,从0.9版开始,批量文档不再以这种方式工作。

        7
  •  0
  •   Ravinder Payal    8 年前

    只需对事务使用sqlite类型的轻量级解决方案,当事务成功完成时复制它,并在sqlite中标记它已复制

    SQLite表

    txn_id    , txn_attribute1, txn_attribute2,......,txn_status
    dhwdhwu$sg1   x                    y               added/replicated
    

    您还可以删除成功复制的事务。