43

我知道我无法锁定单个 mongodb 文档,实际上也没有办法锁定集合。

但是,我遇到了这种情况,我认为我需要某种方法来防止多个线程(或进程,这并不重要)修改文档。这是我的场景。

我有一个包含 A 类型对象的集合。我有一些代码可以检索 A 类型的文档,在数组中添加一个元素,该元素是文档 ( a.arr.add(new Thing()) 的属性,然后将文档保存回 mongodb。这段代码是并行的,我的应用程序中的多个线程可以执行这些操作,现在没有办法阻止线程在同一个文档上并行执行这些操作。这很糟糕,因为其中一个线程可能会覆盖另一个线程的工作。

我确实使用存储库模式来抽象对 mongodb 集合的访问,因此我只有 CRUD 操作可供我使用。

现在我想起来了,也许这是存储库模式的限制,而不是 mongodb 的限制给我带来了麻烦。无论如何,我怎样才能使这段代码“线程安全”?我想这个问题有一个众所周知的解决方案,但是对于 mongodb 和存储库模式是新手,我不会立即看到它。

谢谢

4

13 回答 13

22

嘿,我认为现在唯一的方法是添加一个状态参数并使用操作findAndModify(),它使您能够以原子方式修改文档。它有点慢,但应该可以解决问题。

因此,假设您添加了一个状态属性,当您检索文档时,将状态从“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。因此,当您完成更新时,只需再次保存/更新所有内容。

您需要注意的唯一想法是您一次只能修改一个文档。

希望能帮助到你。

于 2012-06-18T06:20:14.140 回答
16

在进行 mongodb 升级时偶然发现了这个问题。与问这个问题时不同,现在 mongodb 支持开箱即用的文档级别锁定。

来自:http ://docs.mongodb.org/manual/faq/concurrency/

“MongoDB 中的锁有多精细?

在 3.0 版中更改。

从 3.0 版开始,MongoDB 附带了 WiredTiger 存储引擎,该引擎对大多数读写操作使用乐观并发控制。WiredTiger 仅在全局、数据库和集合级别使用意图锁。当存储引擎检测到两个操作之间的冲突时,会引发写入冲突,导致 MongoDB 透明地重试该操作。”

于 2015-06-24T20:15:59.777 回答
7

“医生,我这样做的时候很痛

“那就不要那样!”

基本上,您所描述的内容听起来像是您在那里有一个串行依赖项——MongoDB 或其他什么,您的算法有一个必须序列化操作的点。这将是一个固有的瓶颈,如果你绝对必须这样做,你将不得不安排某种信号量来保护它。

所以,要看的地方是你的算法。你能消除它吗?例如,您能否通过某种冲突解决方法来处理它,例如“将记录放入本地更新;存储记录”,以便在存储后新记录将成为该键上的记录?

于 2012-06-18T02:18:00.313 回答
7

当您想要使某些东西成为线程安全的东西时,经典的解决方案是使用锁(互斥锁)。这也称为悲观锁定,而不是此处描述的乐观锁定

在某些情况下,悲观锁定更有效(此处有更多详细信息)。它也更容易实现(乐观锁定的主要困难是从碰撞中恢复)。

MongoDB 不提供锁机制。但这可以在应用程序级别轻松实现(即在您的代码中):

  1. 获取锁
  2. 阅读文件
  3. 修改文件
  4. 写文件
  5. 释放锁

锁的粒度可以不同:全局、特定于集合、特定于记录/文档。锁越具体,其性能损失就越小。

于 2013-11-23T18:32:57.203 回答
4

回答我自己的问题,因为我在互联网上进行研究时找到了解决方案。

我认为我需要做的是使用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();
}
于 2012-06-18T15:25:11.637 回答
4

更新: MongoDB 3.2.2 使用 WiredTiger 存储实现作为默认引擎,MongoDB 在文档级别使用默认锁定。它是在 3.0 版中引入的,但在 3.2.2 版中是默认的。因此 MongoDB 现在有文档级锁定。

于 2016-02-26T14:22:17.683 回答
3

从 4.0 开始,MongoDB 支持副本集的事务。MongoDB 4.2 将支持分片集群。使用事务,如果发生写入冲突,数据库更新将被中止,从而解决您的问题。

事务在性能方面的成本要高得多,所以不要将事务作为糟糕的 NoSQL 模式设计的借口!

于 2018-12-16T06:49:42.887 回答
2

另一种方法是就地更新

例如:

http://www.mongodb.org/display/DOCS/Updating#comment-41821928

db.users.update( { level: "Sourcerer" }, { '$push' : { 'inventory' : 'magic wand'} }, false, true );

这会将“魔杖”推入所有“Sourcerer”用户的库存数组。对每个文档/用户的更新是原子的。

于 2012-10-07T09:07:40.247 回答
2

如果您的系统具有 > 1 个服务器,那么您将需要一个分布式锁。

我更喜欢使用Hazelcast

在保存时,您可以通过实体 id 获取 Hazelcast 锁,获取和更新数据,然后释放锁。

例如: https ://github.com/azee/template-api/blob/master/template-rest/src/main/java/com/mycompany/template/scheduler/SchedulerJob.java

只需使用lock.lock()而不是lock.tryLock()

在这里你可以看到如何在你的 spring 上下文中配置 Hazelcast:

https://github.com/azee/template-api/blob/master/template-rest/src/main/resources/webContext.xml

于 2014-04-03T15:17:03.460 回答
0

我没有在另一个问题中写这个问题,而是尝试回答这个问题:我想知道这个 WiredTiger Storage 是否会处理我在这里指出的问题: Limit inserts in mongodb

于 2017-04-06T16:53:35.460 回答
0

如果数组中元素的顺序对您来说并不重要,那么$push运算符应该足够安全,以防止线程覆盖彼此的更改。

于 2018-03-21T02:53:34.290 回答
0

我有一个类似的问题,我有同一个应用程序的多个实例,这些实例将从数据库中提取数据(顺序无关紧要;所有文档都必须更新 - 高效),处理它并写回结果。然而,在没有任何锁定的情况下,所有实例显然都提取了相同的文档,而不是智能地分配他们的劳动力。

我试图通过在应用程序级别上实现锁定来解决它,这将locked在当前正在编辑的相应文档中添加一个 -field,这样我的应用程序的其他实例就不会选择相同的文档并通过执行来浪费时间与其他实例相同的操作。

但是,当运行我的应用程序的数十个或更多实例时,读取文档(使用find())和将locked-field 设置为true(使用update())where to long 之间的时间跨度仍然从数据库中提取相同的文档,这让我想到了加速使用多个实例进行工作毫无意义。

以下 3 条建议可能会根据您的情况解决您的问题:

  1. 使用findAndModify() 因为使用该函数的读写操作是原子的。从理论上讲,您的应用程序的一个实例所请求的文档应该对其他实例显示为已锁定。并且当文档被解锁并且再次对其他实例可见时,它也会被修改。

  2. 但是,如果您需要在find()读写update()操作之间做其他事情,您可以使用transactions

  3. 或者,如果这不能解决您的问题,那么一些简单的解决方案(可能就足够了)使应用程序大批量提取文档,并使每个实例从该批次中选择一个随机文档并对其进行处理。显然,这种阴暗的解决方案是基于巧合不会影响您的应用程序的效率这一事实。

于 2021-04-20T15:03:21.510 回答
-1

听起来您想使用 MongoDB 的原子运算符:http ://www.mongodb.org/display/DOCS/Atomic+Operations

于 2012-10-30T07:50:35.683 回答