我需要做事务(开始,提交或回滚),锁(选择更新).我怎样才能在文档模型db中执行此操作?
编辑:
案例是这样的:
我想经营拍卖网站.
我认为如何直接购买.
在直接购买中,我必须减少商品记录中的数量字段,但仅限于数量大于零的情况.这就是我需要锁和交易的原因.
我不知道如何在没有锁和/或交易的情况下解决这个问题.
我可以用CouchDB解决这个问题吗?
CouchDB使用"乐观并发"模型.简单来说,这只是意味着您发送文档版本以及更新,如果当前文档版本与您发送的版本不匹配,CouchDB会拒绝更改.
真的,这看起来很简单.您可以为CouchDB重新构建许多基于事务的常规方案.但是,在学习CouchDB时,您确实需要抛弃您的RDBMS领域知识.从更高层次处理问题是有帮助的,而不是试图将Couch塑造成基于SQL的世界.
跟踪库存
您概述的问题主要是库存问题.如果您有一个描述项目的文档,并且它包含"可用数量"字段,您可以处理如下的并发问题:
检索文档,记下_rev
CouchDB发送的属性
如果数量字段大于零,则减少数量字段
使用_rev
属性发回更新的文档
如果_rev
匹配当前存储的号码,请完成!
如果存在冲突(何时_rev
不匹配),请检索最新的文档版本
在这种情况下,有两种可能的故障情景需要考虑.如果最新的文档版本的数量为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
和_rev
)进行迭代,直到我成功申请一个(之前声明的票证将导致更新错误).
降低
function (keys, values, combine) { return values.length; }
此减少功能只返回无人认领inventory_ticket
物品的总数,因此您可以判断有多少"锤子"可供购买.
注意事项
对于您提出的特定问题,此解决方案代表了大约3.5分钟的总体思路.可能有更好的方法来做到这一点!也就是说,它确实大大减少了冲突的更新,并减少了响应新更新冲突的需要.在此模型下,您将不会有多个用户尝试更改主产品条目中的数据.在最糟糕的情况下,您将有多个用户尝试申请单张票,如果您从视图中抓取了其中的几张,则只需转到下一张票并再试一次.
参考:https://wiki.apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB.3F
扩展了MrKurt的答案.对于许多场景,您不需要按顺序兑换库存票.您可以从剩余的票证中随机选择,而不是选择第一张票.给定大量票证和大量并发请求,与那些试图获得第一张票证的人相比,您将大大减少对这些票证的争用.
restfull事务的设计模式是在系统中创建"张力".对于银行帐户交易的流行示例用例,您必须确保更新两个相关帐户的总计:
创建交易文档"将10美元从帐户11223转移到帐户88733".这会在系统中产生张力.
解决所有交易文件的张力扫描问题
如果源帐户未更新,请更新源帐户(-10美元)
如果源帐户已更新但交易文档未显示此信息,则更新交易文档(例如,在文档中设置标记"sourcedone")
如果目标帐户未更新,请更新目标帐户(+10美元)
如果目标帐户已更新但交易文档未显示此内容,则更新交易文档
如果两个帐户都已更新,您可以删除交易文档或将其保留以供审核.
对于所有"张力文件",应在后端过程中进行张力扫描,以保持系统中的张力时间短.在上面的示例中,当第一个帐户已更新但第二个帐户尚未更新时,预计会出现短暂的预期不一致.如果您的Couchdb是分布式的,那么必须考虑到与处理最终一致性相同的方式.
另一种可能的实现方式完全避免了对事务的需求:只需存储张力文档并通过评估每个涉及的张力文档来评估系统状态.在上面的示例中,这意味着帐户的总数仅被确定为涉及此帐户的交易文档中的总和值.在Couchdb中,您可以非常好地将其建模为map/reduce视图.
不,CouchDB通常不适用于事务性应用程序,因为它不支持集群/复制环境中的原子操作.
CouchDB牺牲了事务性能力,有利于可伸缩性.为了进行原子操作,您需要一个中央协调系统,这会限制您的可扩展性.
如果您可以保证只有一个CouchDB实例或者每个修改特定文档的人都连接到同一个CouchDB实例,那么您可以使用冲突检测系统使用上述方法创建一种原子性但是如果您稍后扩展到一个集群或者使用像Cloudant这样的托管服务,它会崩溃,你将不得不重做系统的那一部分.
因此,我的建议是使用CouchDB之外的其他东西来支付帐户余额,这样会更容易.
作为对OP问题的回应,Couch可能不是这里的最佳选择.使用视图是跟踪库存的好方法,但是或多或少不可能达到0.当您阅读视图的结果时,问题是竞争条件,决定您可以使用"hammer-1"项目,然后编写文档来使用它.问题是,如果视图的结果是> 0 hammer-1,则没有原子方法只能编写使用锤子的文档.如果100个用户同时查询视图并看到1个hammer-1,他们都可以写一个文档来使用锤子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函数,它只是将所有行的值与匹配的键相加.
在此视图中,任何doc都可以有一个成员"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把锤子.
使用此模型,您永远不会更新任何数据,只会追加.这意味着没有机会发生更新冲突.所有更新数据的交易问题都消失了:)
这个模型的另一个好处是DB中的任何文档都可以从库存中添加和减去项目.这些文档中可以包含各种其他数据.您可能有一个"发货"文档,其中包含有关收到的日期和时间,仓库,接收员工等的大量数据,只要该文档定义了InventoryChange,它就会更新库存.正如"销售"文档和"DamagedItem"文档等那样.查看每个文档,他们阅读得非常清楚.视图处理所有艰苦的工作.