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

编辑:

案例是这样的:

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

我可以用 CouchDB 解决这个问题吗?

有帮助吗?

解决方案

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

这确实很简单。您可以为 CouchDB 重新构建许多基于正常事务的场景。不过,在学习 CouchDB 时,您确实需要放弃 RDBMS 领域知识。从更高的层次解决问题比尝试将 Couch 塑造成基于 SQL 的世界更有帮助。

跟踪库存

您所描述的问题主要是库存问题。如果您有一个描述某个项目的文档,并且它包含“可用数量”字段,您可以像这样处理并发问题:

  1. 检索文档,记下 _rev CouchDB 发送的属性
  2. 如果数量字段大于零,则减少数量字段
  3. 使用以下命令发回更新后的文档 _rev 财产
  4. 如果 _rev 与当前存储的数字匹配,完成!
  5. 如果有冲突(当 _rev 不匹配),检索最新的文档版本

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

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

我将从“主产品”文档开始,其中包含所有描述符数据(名称、图片、描述、价格等)。然后,我将为每个特定实例添加一个“库存票”文档,其中包含以下字段: product_keyclaimed_by. 。如果您要销售一种型号的锤子,并且有 20 个要出售,您可能会拥有包含以下密钥的文档: hammer-1, hammer-2, 等,代表每个可用的锤子。

然后,我将创建一个视图,为我提供可用锤子的列表,并使用减少功能让我看到“总计”。这些完全是即兴的,但应该让您了解工作视图的样子。

地图

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

其他提示

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

安静事务的设计模式是在系统中创建“张力”。对于银行账户交易的流行示例用例,您必须确保更新两个相关账户的总额:

  • 创建交易单据“从账户11223转账10美元至账户88733”。这会在系统中造成紧张。
  • 解决所有交易文件的紧张扫描问题
    • 如果源账户尚未更新则更新源账户(-10 USD)
    • 如果源账户已更新,但交易文档未显示这一点,则更新交易文档(例如在文档中设置标志“sourcedone”)
    • 如果目标账户尚未更新则更新目标账户(+10 USD)
    • 如果目标账户已更新但交易文档未显示,则更新交易文档
    • 如果两个账户都已更新,您可以删除交易文档或保留它以供审核。

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

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

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

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

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

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

作为对OP问题的回应,Couch可能不是这里的最佳选择。使用视图是跟踪库存的好方法,但限制为 0 或多或少是不可能的。问题是当您读取视图结果时的竞争条件,决定可以使用“hammer-1”项目,然后编写文档来使用它。问题是,如果视图的结果是存在 > 0 个hammer-1,则没有原子方法可以仅编写文档来使用hammer-1。如果 100 个用户同时查询视图并看到 1 个 Hammer-1,则他们都可以编写文档来使用 Hammer 1,从而得到 -99 个 Hammer-1。实际上,竞争条件将相当小 - 如果您的数据库运行本地主机,则竞争条件非常小。但是,一旦扩展并拥有异地数据库服务器或集群,问题就会变得更加明显。无论如何,在一个与金钱相关的关键系统中出现这种竞争条件是不可接受的。

MrKurt 回复的更新(可能只是过时了,或者他可能不知道某些 CouchDB 功能)

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

您不需要在视图中发出 docid 和 rev。当您检索查看结果时,您可以免费获得这两个内容。发出它们——尤其是像字典这样的冗长格式——只会让你的视图变得不必要的大。

跟踪库存余额的简单视图应该看起来更像这样(也是我的想法)

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

而reduce函数就更简单了

_sum

这使用了一个 内置减少功能 它只是将具有匹配键的所有行的值相加。

在此视图中,任何文档都可以有一个成员“InventoryChange”,它将product_key映射到它们的总库存中的更改。IE。

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

将添加 10 个hammer_1234 和 25 个 saw_4321。

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

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

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

该模型的另一个好处是数据库中的任何文档都可以从库存中添加和减少项目。这些文档中可以包含各种其他数据。您可能有一个“发货”文档,其中包含有关接收日期和时间、仓库、接收员工等的大量数据。只要该文档定义了 InventoryChange,它就会更新库存。“销售”文档和“损坏物品”文档等也可以。每一份文件,他们都读得很清楚。并且视图可以处理所有艰苦的工作。

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

基本上,您可以在单个发布请求中创建/更新/删除一堆文档 URI /{dbname}/_bulk_docs 他们要么全部成功,要么全部失败。不过,该文件确实警告说,这种行为将来可能会改变。

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

只需使用SQlite这种轻量级的事务解决方案,当事务完成时成功复制它,并在SQLite中将其标记为已复制

SQLite表

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top