database 我可以在 CouchDB 中进行事务和锁定吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/299723/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 07:05:04  来源:igfitidea点击:

Can I do transactions and locks in CouchDB?

databasecouchdbnosql

提问by user2427

I need to do transactions (begin, commit or rollback), locks (select for update). How can I do it in a document model db?

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

Edit:

编辑:

The case is this:

情况是这样的:

  • I want to run an auctions site.
  • And I think how to direct purchase as well.
  • In a direct purchase I have to decrement the quantity field in the item record, but only if the quantity is greater than zero. That is why I need locks and transactions.
  • I don't know how to address that without locks and/or transactions.
  • 我想经营一个拍卖网站。
  • 我也想如何直接购买。
  • 在直接购买中,我必须减少项目记录中的数量字段,但前提是数量大于零。这就是为什么我需要锁和事务。
  • 我不知道如何在没有锁和/或事务的情况下解决这个问题。

Can I solve this with CouchDB?

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

回答by MrKurt

No. CouchDB uses an "optimistic concurrency" model. In the simplest terms, this just means that you send a document version along with your update, and CouchDB rejects the change if the current document version doesn't match what you've sent.

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

It's deceptively simple, really. You can reframe many normal transaction based scenarios for CouchDB. You do need to sort of throw out your RDBMS domain knowledge when learning CouchDB, though. It's helpful to approach problems from a higher level, rather than attempting to mold Couch to a SQL based world.

这看似简单,真的。您可以为 CouchDB 重构许多基于普通事务的场景。不过,在学习 CouchDB 时,您确实需要抛开 RDBMS 领域知识。从更高的层次解决问题是有帮助的,而不是试图将 Couch 塑造成基于 SQL 的世界。

Keeping track of inventory

跟踪库存

The problem you outlined is primarily an inventory issue. If you have a document describing an item, and it includes a field for "quantity available", you can handle concurrency issues like this:

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

  1. Retrieve the document, take note of the _revproperty that CouchDB sends along
  2. Decrement the quantity field, if it's greater than zero
  3. Send the updated document back, using the _revproperty
  4. If the _revmatches the currently stored number, be done!
  5. If there's a conflict (when _revdoesn't match), retrieve the newest document version
  1. 检索文档,注意_revCouchDB 发送的属性
  2. 减少数量字段,如果它大于零
  3. 使用_rev属性将更新后的文档发回
  4. 如果_rev匹配当前存储的号码,完成!
  5. 如果有冲突(当_rev不匹配时),检索最新的文档版本

In this instance, there are two possible failure scenarios to think about. If the most recent document version has a quantity of 0, you handle it just like you would in a RDBMS and alert the user that they can't actually buy what they wanted to purchase. If the most recent document version has a quantity greater than 0, you simply repeat the operation with the updated data, and start back at the beginning. This forces you to do a bit more work than an RDBMS would, and could get a little annoying if there are frequent, conflicting updates.

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

Now, the answer I just gave presupposes that you're going to do things in CouchDB in much the same way that you would in an RDBMS. I might approach this problem a bit differently:

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

I'd start with a "master product" document that includes all the descriptor data (name, picture, description, price, etc). Then I'd add an "inventory ticket" document for each specific instance, with fields for product_keyand claimed_by. If you're selling a model of hammer, and have 20 of them to sell, you might have documents with keys like hammer-1, hammer-2, etc, to represent each available hammer.

我将从包含所有描述符数据(名称、图片、描述、价格等)的“主产品”文档开始。然后,我会为每个特定实例添加一个“库存单”文档,其中包含product_key和字段claimed_by。如果您销售的锤子的典范,并让他们的20卖,你可能有一个像钥匙文件hammer-1hammer-2等等,来表示每个可用锤子。

Then, I'd create a view that gives me a list of available hammers, with a reduce function that lets me see a "total". These are completely off the cuff, but should give you an idea of what a working view would look like.

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

Map

地图

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

This gives me a list of available "tickets", by product key. I could grab a group of these when someone wants to buy a hammer, then iterate through sending updates (using the idand _rev) until I successfully claim one (previously claimed tickets will result in an update error).

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

Reduce

降低

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

This reduce function simply returns the total number of unclaimed inventory_ticketitems, so you can tell how many "hammers" are available for purchase.

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

Caveats

注意事项

This solution represents roughly 3.5 minutes of total thinking for the particular problem you've presented. There may be better ways of doing this! That said, it does substantially reduce conflicting updates, and cuts down on the need to respond to a conflict with a new update. Under this model, you won't have multiple users attempting to change data in primary product entry. At the very worst, you'll have multiple users attempting to claim a single ticket, and if you've grabbed several of those from your view, you simply move on to the next ticket and try again.

这个解决方案代表了对您提出的特定问题的大约 3.5 分钟的总思考时间。可能有更好的方法来做到这一点!也就是说,它确实大大减少了冲突更新,并减少了用新更新来响应冲突的需要。在此模型下,您不会有多个用户尝试更改主要产品条目中的数据。在最糟糕的情况下,您将有多个用户尝试索取一张票,如果您已经从您的视图中抓取了其中的几个,您只需转到下一张票并重试。

Reference: https://wiki.apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB.3F

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

回答by Kerr

Expanding on MrKurt's answer. For lots of scenarios you don't need to have stock tickets redeemed in order. Instead of selecting the first ticket, you can select randomly from the remaining tickets. Given a large number tickets and a large number of concurrent requests, you will get much reduced contention on those tickets, versus everyone trying to get the first ticket.

扩展 MrKurt 的回答。对于许多场景,您不需要按顺序兑换股票。您可以从剩余的门票中随机选择,而不是选择第一张票。给定大量票证和大量并发请求,您将大大减少对这些票证的争用,而不是每个人都试图获得第一张票证。

回答by ordnungswidrig

A design pattern for restfull transactions is to create a "tension" in the system. For the popular example use case of a bank account transaction you must ensure to update the total for both involved accounts:

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

  • Create a transaction document "transfer USD 10 from account 11223 to account 88733". This creates the tension in the system.
  • To resolve any tension scan for all transaction documents and
    • If the source account is not updated yet update the source account (-10 USD)
    • If the source account was updated but the transaction document does not show this then update the transaction document (e.g. set flag "sourcedone" in the document)
    • If the target account is not updated yet update the target account (+10 USD)
    • If the target account was updated but the transaction document does not show this then update the transaction document
    • If both accounts have been updated you can delete the transaction document or keep it for auditing.
  • 创建一个交易单据“从账户 11223 转账 10 美元到账户 88733”。这会在系统中产生张力。
  • 解决所有交易文件的任何张力扫描和
    • 如果源帐户尚未更新,请更新源帐户(-10 美元)
    • 如果源帐户已更新,但交易文档未显示此内容,则更新交易文档(例如,在文档中设置标记“sourcedone”)
    • 如果目标账户尚未更新,则更新目标账户(+10 美元)
    • 如果目标帐户已更新,但交易文件未显示此内容,则更新交易文件
    • 如果两个帐户都已更新,您可以删除交易文档或保留以供审核。

The scanning for tension should be done in a backend process for all "tension documents" to keep the times of tension in the system short. In the above example there will be a short time anticipated inconsistence when the first account has been updated but the second is not updated yet. This must be taken into account the same way you'll deal with eventual consistency if your Couchdb is distributed.

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

Another possible implementation avoids the need for transactions completely: just store the tension documents and evaluate the state of your system by evaluating every involved tension document. In the example above this would mean that the total for a account is only determined as the sum values in the transaction documents where this account is involved. In Couchdb you can model this very nicely as a map/reduce view.

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

回答by Dobes Vandermeer

No, CouchDB is not generally suitable for transactional applications because it doesn't support atomic operations in a clustered/replicated environment.

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

CouchDB sacrificed transactional capability in favor of scalability. In order to have atomic operations you need a central coordination system, which limits your scalability.

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

If you can guarantee you only have one CouchDB instance or that everyone modifying a particular document connects to the same CouchDB instance then you could use the conflict detection system to create a sort of atomicity using methods described above but if you later scale up to a cluster or use a hosted service like Cloudant it will break down and you'll have to redo that part of the system.

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

So, my suggestion would be to use something other than CouchDB for your account balances, it will be much easier that way.

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

回答by wallacer

As a response to the OP's problem, Couch is probably not the best choice here. Using views is a great way to keep track of inventory, but clamping to 0 is more or less impossible. The problem being the race condition when you read the result of a view, decide you're ok to use a "hammer-1" item, and then write a doc to use it. The problem is that there's no atomic way to only write the doc to use the hammer if the result of the view is that there are > 0 hammer-1's. If 100 users all query the view at the same time and see 1 hammer-1, they can all write a doc to use a hammer 1, resulting in -99 hammer-1's. In practice, the race condition will be fairly small - really small if your DB is running localhost. But once you scale, and have an off site DB server or cluster, the problem will get much more noticeable. Regardless, it's unacceptable to have a race condition of that sort in a critical - money related system.

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

An update to MrKurt's response (it may just be dated, or he may have been unaware of some CouchDB features)

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

A view is a good way to handle things like balances / inventories in CouchDB.

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

You don't need to emit the docid and rev in a view. You get both of those for free when you retrieve view results. Emitting them - especially in a verbose format like a dictionary - will just grow your view unnecessarily large.

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

A simple view for tracking inventory balances should look more like this (also off the top of my head)

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

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

And the reduce function is even more simple

而reduce函数就更简单了

_sum

This uses a built in reduce functionthat just sums the values of all rows with matching keys.

这使用了一个内置的 reduce 函数,该函数只是将所有具有匹配键的行的值相加。

In this view, any doc can have a member "InventoryChange" that maps product_key's to a change in the total inventory of them. ie.

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

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

Would add 10 hammer_1234's and 25 saw_4321's.

将添加 10 个hammer_1234 和25 个saw_4321。

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

Would burn 5 hammers from the inventory.

将从库存中烧掉 5 个锤子。

With this model, you're never updating any data, only appending. This means there's no opportunity for update conflicts. All the transactional issues of updating data go away :)

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

Another nice thing about this model is that ANY document in the DB can both add and subtract items from the inventory. These documents can have all kinds of other data in them. You might have a "Shipment" document with a bunch of data about the date and time received, warehouse, receiving employee etc. and as long as that doc defines an InventoryChange, it'll update the inventory. As could a "Sale" doc, and a "DamagedItem" doc etc. Looking at each document, they read very clearly. And the view handles all the hard work.

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

回答by Evan

Actually, you can in a way. Have a look at the HTTP Document APIand scroll down to the heading "Modify Multiple Documents With a Single Request".

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

Basically you can create/update/delete a bunch of documents in a single post request to URI /{dbname}/_bulk_docsand they will either all succeed or all fail. The document does caution that this behaviour may change in the future, though.

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

EDIT: As predicted, from version 0.9 the bulk docs no longer works this way.

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

回答by Ravinder Payal

Just use SQlite kind of lightweight solution for transactions, and when the transaction is completed successfully replicate it, and mark it replicated in SQLite

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

SQLite table

SQLite 表

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

You can also delete the transactions which are replicated successfully.

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