我知道我无法锁定单个mongodb文档,实际上也无法锁定集合。
但是,我有这种情况,我认为我需要一些方法来防止多个线程(或进程,这并不重要)修改文档。这是我的情景。
我有一个包含A类对象的集合。我有一些代码可以检索类型为A的文档,在数组中添加一个元素,该元素是文档的属性(a.arr.add(new Thing()
),然后保存回来mongodb的文件。这段代码是并行的,我的应用程序中的多个线程可以执行这些操作,现在没有办法阻止线程在同一文档上并行执行这些操作。这很糟糕,因为其中一个线程可能会覆盖另一个线程的工作。
我确实使用存储库模式来抽象对mongodb集合的访问,所以我只处理了CRUD操作。
现在我考虑一下,也许这是对存储库模式的限制,而不是mongodb的限制导致我的麻烦。无论如何,我怎样才能使这段代码“线程安全”?我想这个问题有一个众所周知的解决方案,但是对mongodb和存储库模式不熟悉,我不会马上看到它。
由于
答案 0 :(得分:12)
嘿,我现在认为唯一的方法是添加一个状态参数并使用操作findAndModify(),这使您能够以原子方式修改文档。它有点慢,但应该这样做。
因此,假设您添加了一个状态attribut,当您检索文档时,将状态从“IDLE”更改为“PROCESSING”。然后更新文档并将其保存回集合,再次将状态更新为“IDLE”。
代码示例:
var doc = db.runCommand({
"findAndModify" : "COLLECTION_NAME",
"query" : {"_id": "ID_DOCUMENT", "status" : "IDLE"},
"update" : {"$set" : {"status" : "RUNNING"} }
}).value
将COLLECTION_NAME和ID_DOCUMENT更改为正确的值。默认情况下,findAndModify()返回旧值,这意味着客户端的状态值仍然是IDLE。因此,当您完成更新后,只需再次保存/更新所有内容。
您唯一需要注意的是,您一次只能修改一个文档。
希望它有所帮助。
答案 1 :(得分:7)
在进行mongodb升级时偶然发现了这个问题。与此问题不同,现在mongodb支持开箱即用的文档级锁定。
来自:{{3}}
" MongoDB中锁的粒度是多少?
在版本3.0中更改。
从版本3.0开始,MongoDB附带了WiredTiger存储引擎,该引擎使用乐观并发控制进行大多数读写操作。 WiredTiger仅在全局,数据库和集合级别使用意图锁。当存储引擎检测到两个操作之间发生冲突时,会发生写入冲突,导致MongoDB透明地重试该操作。"
答案 2 :(得分:5)
“医生,当我这个”
时会疼“那就不要做了!”
基本上,你所描述的内容听起来就像你有一个串行依赖 - MongoDB或其他什么,你的算法有一个点,操作必须序列化。这将是一个固有的瓶颈,如果你绝对必须这样做,你将不得不安排某种信号量来保护它。
所以,要看的地方是你的算法。你能消除吗?例如,您是否可以通过某种冲突解决方案来处理它,例如“将记录转换为本地'更新;存储记录”,以便在存储之后新记录将是获取该密钥的记录?
答案 3 :(得分:3)
当您想要制作线程安全的东西时,经典解决方案是使用锁(互斥锁)。 这也称为悲观锁定,而不是乐观锁定描述here。
有些情况下悲观锁定更有效(更多细节here)。它也更容易实现(乐观锁定的主要困难是从碰撞中恢复)。
MongoDB不提供锁定机制。但这可以在应用程序级别(即代码中)轻松实现:
锁的粒度可以不同:全局,特定于集合,特定于记录/文档。锁定越具体,其性能损失就越小。
答案 4 :(得分:2)
回答我自己的问题是因为我在互联网上进行研究时找到了解决方案。
我认为我需要做的是使用Optimistic Concurency Control。
它包括向每个文档添加时间戳,哈希或其他唯一标识符(我将使用UUID)。每次修改文档时都必须修改唯一标识符。在更新文档之前,我会做这样的事情(伪代码):
var oldUUID = doc.uuid;
doc.uuid = new UUID();
BeginTransaction();
if (GetDocUUIDFromDatabase(doc.id) == oldUUID)
{
SaveToDatabase(doc);
Commit();
}
else
{
// Document was modified in the DB since we read it. We can't save our changes.
RollBack();
throw new ConcurencyException();
}
答案 5 :(得分:2)
另一种方法是in place update
代表:
http://www.mongodb.org/display/DOCS/Updating#comment-41821928
db.users.update( { level: "Sourcerer" }, { '$push' : { 'inventory' : 'magic wand'} }, false, true );
将“魔杖”推入所有“Sourcerer”用户的库存数组。对每个文档/用户的更新是原子的。
答案 6 :(得分:2)
<强>更新强> 使用MongoDB 3.2.2作为默认引擎使用WiredTiger Storage实现,MongoDB在文档级别使用默认锁定。它在3.0版本中引入,但在版本3.2.2中默认使用。因此,MongoDB现在具有文档级锁定功能。
答案 7 :(得分:0)
如果您的系统包含&gt; 1台服务器然后你需要一个分配锁。
我更喜欢使用Hazelcast。
保存时,您可以通过实体ID获取Hazelcast锁定,获取并更新数据,然后释放锁定。
只需使用lock.lock()
代替lock.tryLock()
在这里,您可以看到如何在春季环境中配置Hazelcast:
https://github.com/azee/template-api/blob/master/template-rest/src/main/resources/webContext.xml
答案 8 :(得分:0)
Instead of writing the question in another question, I try to answer this one: I wonder if this WiredTiger Storage will handle the problem I pointed out here: Limit inserts in mongodb
答案 9 :(得分:0)
如果数组中元素的顺序对您不重要,那么$push运算符应足够安全,以防止线程覆盖其他更改。
答案 10 :(得分:0)
从4.0开始,MongoDB支持Transactions用于副本集。对分片群集的支持将在MongoDB 4.2中提供。使用事务,如果发生冲突的写操作,提交的数据库更新将中止,从而解决了您的问题。
就性能而言,事务的成本要高得多,所以不要以事务为借口来进行不良的NoSQL模式设计!
答案 11 :(得分:0)
我遇到了类似的问题,我有同一个应用程序的多个实例,这些实例将从数据库中提取数据(顺序无关紧要;所有文档都必须更新 - 有效地),处理它并写回结果。然而,在没有任何锁定的情况下,所有实例显然都提取了相同的文档,而不是智能地分配他们的劳动力。
我试图通过在应用程序级别实现锁定来解决它,这会在当前正在编辑的相应文档中添加一个 locked
字段,以便我的应用程序的其他实例不会选择相同的通过执行与其他实例相同的操作来记录并浪费时间。
但是,当我的应用程序运行数十个或更多实例时,读取文档(使用 find()
)和将 locked
字段设置为 true
(使用 {{1} }}) 在哪里长并且实例仍然从数据库中提取相同的文档,这使得我使用多个实例来加速工作的想法毫无意义。
以下 3 条建议可能会根据您的情况解决您的问题:
使用 update()
() 因为使用该函数的读写操作是原子的。从理论上讲,您的应用程序的一个实例请求的文档应该对其他实例显示为已锁定。并且当文档被解锁并再次对其他实例可见时,它也会被修改。
但是,如果您需要在读取 findAndModify
和写入 find()
操作之间执行其他操作,您可以使用 transactions。
或者,如果这不能解决您的问题,一些奶酪解决方案(这可能就足够了)使应用程序大批量提取文档并使每个实例从该批次中随机选择一个文档并继续工作它。显然,这种阴暗的解决方案是基于这样一个事实:巧合不会影响应用程序的效率。
答案 12 :(得分:-1)
听起来你想使用MongoDB的原子操作符:http://www.mongodb.org/display/DOCS/Atomic+Operations