我可以在CouchDB中进行交易和锁定吗?


81

我需要执行事务(开始,提交或回滚),锁(选择更新)。如何在文档模型数据库中做到这一点?

编辑:

情况是这样的:

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

我可以用CouchDB解决吗?

Answers:


145

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

真的,这看似简单。您可以为CouchDB重新构造许多基于常规事务的方案。但是,在学习CouchDB时,您确实需要投入RDBMS领域的知识。从更高层次解决问题很有帮助,而不是尝试将Couch塑造为基于SQL的世界。

跟踪库存

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

  1. 检索文档,注意 _revCouchDB发送属性
  2. 如果数量字段大于零,则减少数量字段
  3. 使用 _rev属性
  4. 如果 _rev匹配当前存储的号码,请完成!
  5. 如果存在冲突(_rev不匹配时),请获取最新的文档版本

在这种情况下,有两种可能的故障场景需要考虑。如果最新文档版本的数量为0,则可以像在RDBMS中一样处理它,并警告用户他们实际上无法购买他们想要购买的东西。如果最新文档版本的数量大于0,则只需对更新的数据重复该操作,然后从头开始。这迫使您比RDBMS做更多的工作,并且如果有频繁的,相互冲突的更新,可能会有些烦人。

现在,我刚刚给出的答案假定您将以与RDBMS中几乎相同的方式在CouchDB中进行操作。我对这个问题的处理方式可能有所不同:

我将从包含所有描述符数据(名称,图片,描述,价格等)的“主产品”文档开始。然后,我将为每个特定实例添加一个“库存票据”文档,其中包含product_key和字段claimed_by。如果您要出售一把锤子模型,并且要出售20支锤子,则可能会有带有诸如hammer-1hammer-2等等,来表示每个可用锤子。

然后,我将创建一个视图,该视图为我提供可用的锤子列表,并带有一个reduce函数,使我可以看到“总计”。这些完全没有用,但是应该让您了解工作视图的外观。

地图

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

这给了我按产品密钥列出的可用“机票”列表。当有人想要购买锤子时,我可以抓住其中的一组,然后通过发送更新(使用id_rev)进行迭代,直到我成功索取一个(以前索取的票证将导致更新错误)。

降低

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


4
对我来说,尚不清楚您尝试依次尝试拥有“票证”与仅重试读取/修改/写入以更新主实体相比,是否有重大改进。当然,这似乎不值得额外的开销,特别是如果您有大量库存。
尼克·约翰逊,

4
从我的角度来看,票证约定的构建“更简单”。在主条目上失败的更新要求您重新加载文档,再次执行操作,然后保存。凭单可以让您尝试并“声明”某些东西,而不必请求更多数据。
MrKurt

另外,这取决于您担心哪种开销。您要么要争夺更多争用,要么要有额外的存储需求。鉴于门票也可以是购买记录的两倍,所以我不知道您会想到多少存储问题。
MrKurt

2
我正在编辑产品文档的数量字段。然后,例如,如果数量= 2K,那么我必须创建数千个“门票”。然后我减少数量,必须删除一些票证。听起来对我来说完全放松。在基本用例中,很多头痛。也许我缺少了一些东西,但是为什么不恢复以前删除的事务行为,只需将它与_bulk_docs?reject_on_conflict = true之类的东西设为可选。在单主机配置中非常有用。
山姆

3
@mehaase:请阅读:guide.couchdb.org/draft/recipes.html,答案归结为ouchdb的内部数据结构“您从不更改数据,只需添加新数据”。在您的方案中,这意味着为借项从一个帐户创建一个(原子)交易到一个传输中的帐户,然后向前(或向后)从该传输中的帐户创建第二(原子)交易。真正的银行就是这样做的。每一步始终得到记录。
Fabian Zeindl 2012年

26

扩展MrKurt的答案。在许多情况下,您不需要按顺序赎回库存票证。除了选择第一个票证,您还可以从其余票证中随机选择。给定大量票证和大量并发请求,与试图获得第一张票证的每个人相比,您在这些票证上的争用将大大减少。


21

完全交易的设计模式是在系统中创建一个“张力”。对于银行帐户交易的流行示例用例,您必须确保更新两个相关帐户的总数:

  • 创建交易凭证“将10美元从帐户11223转移到帐户88733”。这在系统中产生了张力。
  • 解决所有交易单据的紧张扫描,并
    • 如果源帐户尚未更新,请更新源帐户(-10 USD)
    • 如果源帐户已更新,但交易文档未显示此信息,则更新交易文档(例如,在文档中设置标志“ sourcedone”)
    • 如果目标帐户尚未更新,请更新目标帐户(+10 USD)
    • 如果目标帐户已更新,但交易凭证未显示此信息,则更新交易凭证
    • 如果两个帐户都已更新,则可以删除交易凭证或保留该凭证以进行审核。

应当在后端过程中对所有“张力文档”进行张力扫描,以使系统中的张力时间更短。在上面的示例中,当第一个帐户已更新但第二个帐户尚未更新时,预计会出现短暂的不一致。如果分发了Couchdb,则必须以与最终一致性相同的方式来考虑这一点。

另一种可能的实现方式完全避免了交易需求:只需存储张力文档并通过评估每个涉及的张力文档来评估系统状态。在上面的示例中,这意味着帐户的总数仅确定为涉及该帐户的交易文档中的总和。在Couchdb中,您可以很好地将其建模为地图/缩小视图。


5
但是,如果帐户借记但张力文档未更改,该怎么办?如果这两点之间不是原子的,那么任何故障情况都会导致永久性的矛盾,对吗?关于流程的某些事情必须是原子的,这就是交易的重点。
伊恩·瓦利

是的,您是正确的,在这种情况下-尽管紧张局势没有解决-但会出现不一致的情况。但是,这种矛盾只是暂时的,直到下次对张力文档的扫描检测到这种情况为止。在这种情况下,这就是时间上的最终一致性。只要您先降低源帐户的数量,然后再增加目标帐户的数量,这是可以接受的。但要注意:张力文档不会在REST之上为您提供ACID事务。但是,它们可以在纯REST和ACID之间进行权衡。
ordnungswidrig

4
想象每个张力文件都有一个时间戳,帐户文件有一个“最后施加张力”字段-或所施加张力的列表。当您从源帐户借记时,您还更新了“ last-tension-applied”字段。这两个操作是原子操作,因为它们在同一文档中。目标帐户也有一个类似的字段。这样一来,系统始终可以告诉哪些张力文档已应用于哪些帐户。
杰西·哈雷特

1
如何检测源/目标文档是否已更新?如果它在第1步之后失败,然后重新执行又再次失败,依此类推,您将继续扣除源帐户?
2010年

1
@wump:您将需要记录张力文件已应用到帐户。例如,通过将张力凭证id附加到任一帐户的列表属性上。当张力文件涉及的所有帐户均已更新时,将张力文件标记为“完成”或将其删除。之后,可以从所有帐户的列表中删除文档ID。
ordnungswidrig

6

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

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

如果可以保证只有一个CouchDB实例,或者每个修改特定文档的人都连接到同一个CouchDB实例,则可以使用冲突检测系统使用上述方法创建某种原子性,但是如果以后扩展到一个群集或使用Cloudant之类的托管服务,它将崩溃,您必须重做该系统的那部分。

因此,我的建议是使用CouchDB以外的其他方式来保存您的帐户余额,这样会容易得多。


5

作为对OP问题的回应,Couch可能不是此处的最佳选择。使用视图是跟踪库存的一种好方法,但是几乎不可能钳制为0。问题是当您阅读视图结果,确定可以使用“ hammer-1”项目并编写文档以使用它时的竞争条件。问题是,如果视图结果是> 0 Hammer-1,则没有原子方法仅编写文档以使用Hammer。如果100个用户同时查询该视图并看到1个Hammer-1,则他们都可以编写文档以使用Hammer 1,从而得到-99 Hammer-1。实际上,竞争条件将非常小-如果您的数据库正在运行localhost,则竞争条件将非常小。但是,一旦进行扩展并拥有一个非现场数据库服务器或群集,问题就会变得更加明显。

MrKurt的回复的更新(它可能已经过时,或者他可能不知道某些CouchDB功能)

视图是处理CouchDB中的余额/库存之类的好方法。

您无需在视图中发出docid和rev。检索视图结果时,您将免费获得这两个功能。发出它们-特别是像字典这样的冗长格式-只会不必要地扩大您的视野。

跟踪库存余额的简单视图应该更像这样(也离我远去)

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

而reduce功能更加简单

_sum

这使用内置的reduce函数,该函数仅将具有匹配键的所有行的值相加。

在此视图中,任何文档都可以具有成员“ InventoryChange”,该成员将product_key映射到其总库存中的更改。即。

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

将增加10个Hammer_1234和25个saw_4321。

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

将燃烧库存中的5锤。

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

此模型的另一个好处是,数据库中的任何文档都可以从清单中添加和减去项目。这些文档中可以包含各种其他数据。您可能有一个“装运”文档,其中包含有关接收日期和时间,仓库,接收员工等的大量数据,只要该文档定义了InventoryChange,它就会更新库存。就像“销售”文档和“ DamagedItem”文档等一样。在查看每个文档时,他们阅读得非常清楚。该视图处理所有艰苦的工作。


有趣的策略。作为CouchDB的新手,为了计算当前的锤子数量,您需要对公司整个锤子库存变化历史进行映射/缩减。这可能需要数年的更改。CouchDB是否有一些内置功能可以使他表现出色?
chadrik '16

是的,CouchDB中的视图就像一个连续的,持久的映射/缩减。您是正确的,从头开始对大型数据集进行操作会花费一些时间,但是添加新文档后,它们仅更新现有视图,而不必重新计算整个视图。请记住,视图既需要空间又需要CPU。另外,至少当我专业地使用CouchDB时(已经有几年了),仅使用内置的reduce函数(即,这很重要)。_和。自定义Javascript减少功能非常慢
wallacer's

3

实际上,您可以采用某种方式。查看HTTP文档API,然后向下滚动至标题“使用单个请求修改多个文档”。

基本上,您可以在对URI / {dbname} / _ bulk_docs的单个发布请求中创建/更新/删除一堆文档,它们要么全部成功要么全部失败。该文档确实提醒您,这种行为将来可能会改变。

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


这在讨论的情况下并没有真正的帮助,即从多个用户争用单个文档。
Kerr,2009年

3
从CouchDB 0.9开始,批量更新的语义已更改。
巴里·沃克

0

只需使用SQlite类型的轻量级解决方案进行事务处理,然后在事务处理成功完成后将其复制并标记为在SQLite中复制

SQLite表

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.