【问题标题】:MongoDB Database Semaphores and Node.js Process.NextTick()MongoDB 数据库信号量和 Node.js Process.NextTick()
【发布时间】:2013-09-20 23:41:43
【问题描述】:

这可能是一个不同的坏主意,或者我们必须解决数据库并发问题的可能解决方案。

我们有一个方法被调用来更新 mongo 记录。我们看到了一些并发问题 - 进程 A 读取记录,进程 B 读取记录,进程 A 修改并保存记录,进程 B 修改并保存记录。因为 B 在 A 之后读取,在 A 写入之前,它不知道 A 所做的更改,因此我们丢失了来自 A 的数据。

我想知道我们是否不能使用数据库信号量,基本上是集合上的一个字段,它是一个布尔值。如果我们在方法开始时读取记录,并且该字段为真,则它正在被编辑。此时,使用 process.nexttick() 使用相同的数据重新调用该方法。否则,设置信号量,然后继续。

读取和保存之间仍有一些时间,但应该/可能比我们现在做的更快。

变成这样。有什么想法,有人做过这样的事吗?它甚至会起作用吗?

function remove_source(service_id,session, next)
{
    var User = Mongoose.model("User");

    /* get the user, based on the session user id */
    User.findById(session.me,function(err,user_info)
    {
         if (user_info.semaphore === true)
         {
               process.nextTick(remove_source(service_id,session,next));
         }
         else
         {
               user_info.semaphore = true;
               user_info.save(function(err,user_new)
               {
                    if (err) next(err,user_new);
                    else continue_on(null,user_new);
               });
         }

         function continue_on(user_new)
         {
             etc.......
         }

编辑:新代码:

函数现在如下所示。我正在对数组进行单独更新。这当然意味着我现在有可能,如果第一个和第二个事务之间的事务失败,数据不同步。我在想我可以简单地重新保存我在进入函数时检索到的用户对象,覆盖我的更改。如果我没有更改该对象,我不知道 Mongoose/Mongo 是否不会进行保存,将不得不尝试看看。还有什么想法吗?

 var User = Mongoose.model("User");

 /* get the user, based on the session user id */
 User.findById(session.me,function(err,user_info)
 {
      if (err)
      {
           next(err,user_info,null);
           return;
      }

      if (!user_info || user_info.length === 0)
      {
           next(_e("ACCOUNT_NOT_FOUND"),"user_id: " + session.me);
           return;
      }

      var source_service_info = _.where(user_info.credentials, {"source_service_id": service_id});
      var source_service = source_service_info.source_service;

      User.findByIdAndUpdate(session.me,{$pull: {"credentials": {"source_service_id": service_id}}},{},function(err,user_credential_removed)
      {
           if (err)
           {
                next(err,user_info,null);
                return;
           }

           User.findByIdAndUpdate(session.me,{$pull: {"criteria": {"source_service": source_service}}},{},function(err,user_criteria_removed)
           {
                if (err)
                {
                     next(err,user_info,null);
                     return;
                }

                else
                {
                     next(null,user_criteria_removed);
                }
           });
      });
 });

};

【问题讨论】:

  • 您可能只想将一个版本添加到您的文档中,并根据该版本在阅读后未发生变化进行有条件地更新。如果更新失败,您将知道自上次读取后发生了修改。

标签: node.js mongodb concurrency


【解决方案1】:

您的方法的问题在于,它只是缩短了第二个进程可以读取数据的时间,并没有消除问题。

解决方案是将信号量设置为与读取相同的操作。我没有使用 Mongoose,但是在 MongoDB 中,您可以使用 findAndModify 仅在信号量为 false 时返回用户记录,如果为 false,则在一个原子操作中将信号量设置为 true。

如果您不想使用 findAndModify,您可以先进行更新,仅在未设置信号量的情况下将信号量设置为 true(或设置为某个特定的 ID 值,以便您知道它是您的信号量)。然后,如果该过程成功,您可以进行查找(也许将您的信号量 ID 作为查找中的标准传递)。但是,如果在 Mongoose 中可以使用 findAndModify,它会一步完成。

这里描述了一种变体:http://docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations/,您可以在其中执行一种乐观锁定形式,在将旧值更改为新值之前检查旧值是否未更改。

对此有一个变体,它使用单独的表来模拟两阶段提交:http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/

【讨论】:

  • 我看了之后也是这么想的。这并没有给我们带来太多好处。这是我们第一个使用 mongo 的大项目。尝试尽可能安全地进行多笔交易似乎是设计中最丑陋的部分。我确实喜欢你这样做的方式,这是有道理的。我想我们必须从查找和修改中测试结果。我很确定它在 Mongoose 中。
  • 我确实喜欢这种简单性。仍然困扰我的一件事是,如果出现故障,记录就会被锁定。可能可以通过 try/catch 块来处理它,或者只是在下面的错误检查中。
  • 如果事务安全至关重要,MongoDB 将是一些额外的工作。正如 Kyle Banker 在 Mongo In Action manning.com/banker2 中所说,没有人会在 MongoDB 中构建银行的后端。
  • 我是一个老的关系 DBA,这是事实。我们现在正在研究 mongo 的优缺点。简单的开发和应用程序更改无需 RDBMS 升级的优势非常诱人。这将完全取决于应用程序及其需求。但现在,它首先看起来像 Mongo,除非数据的复杂性说是 RDBMS。
  • 我也是一个关系数据库和桌面的老家伙。这是新的魔法,我们的旧观念会阻碍我们。我敢打赌这不是抽象的事务性数据库讨论,而是更新子字段讨论的语法。
【解决方案2】:

已编辑:在下面交换时,这似乎是一个架构和更新问题。问题可能会变成这样:我在一个数组中有一些条目,这些条目的序号索引也与其他一些数组有关。如何执行删除而不出现不匹配?

根据现实世界中的频率与 QA 测试场景的不同,会出现三种最常见的可能性。

  1. 考虑添加一个已删除标志,但保持记录的顺序相同。如果有人切换,请重复使用相同的记录,但可以根据需要进行修复。
  2. 为每个元素使用关联数组(JS 对象)(不是关系世界中的特性)。如果您需要顺序,请添加一个按顺序列出键的数组。两者都有语法更新,而不会触及任何其他已更改的内容,并且不会覆盖对不同字段的更改。
  3. 使用键为数字的关联数组。实际删除不会影响检索。

    东西 = {} 东西[1] = {一些:'细节'} 东西[2] = {一些:'details2'}

曾经 1)您是否对同一字段进行更改?把它变成一个数组,然后推送更改,然后弹出最新的以读取当前值。

2) 您是否更改了不同的字段,但数据却被打败了?然后有更好的语法用于更新。您可以逐个字段更新。

$set: { 'fielda': 'valuea' } 

不会丢失对先前字段的编辑

3) 改变你的架构

4) 更改进程的时间,使它们不会重叠。或者他们在较小的子集中这样做,您可以设法防止重叠。

出于兴趣,我想知道需要哪些多个流程才能对同一记录进行更新?我不使用任何看起来像那样的东西。

【讨论】:

  • 客户端是一个 ipad 客户端,它同时发送多个更改。我们已将客户拥有的项目构建到用户集合中,因为我们经常更改有关用户的信息以及他们同时创建的事物。该文档有许多子文档。它使更新然后将其拆分更加安全。特别是在这种情况下,它是来自各种外部服务的凭据——他们可能会决定在登录时删除一些凭据。客户端同时发送它们而不是一个接一个。我们可以将它们作为单个数组发送。
  • 删除现有凭证的用例不需要有交叉更新冲突——它们在用户记录中的某个点是不同的。听起来您正在对树中太高的字段执行更新。向我们展示更新,这是一个快速修复。
  • 嗨,Jim,是的,我们正在保存整个用户对象。凭据在一个数组中,然后与它们关联的对象在另一个数组中。我们目前正在操作这些数组,然后更新用户对象,然后重新保存整个用户对象。根据您的说法,这可能不是总体上最好的策略。但是,因为它们是数组,并且我们正在删除成员,我可以删除数组中的单个成员吗?我们还喜欢在完成后压缩数组,以便我们有连续的编号,而不是留下空白。因此,编号可能会在保存之间发生变化。
  • 所以,总结一下,如果我们删除成员3,然后压缩,另一个事务已经拉取了数据,那么数组就不再排队了。数组压缩的问题是 node 不乐意引用不存在的成员,我们倾向于使用 for 循环来访问它们。
  • 查看了 Mongoose 文档,他们确实有更新,理论上允许设置一个或多个字段,但可能有一些其他不愉快的副作用。因此,我假设进行单个字段更新意味着不触及文档的其余部分。保存整个对象,将重新保存所有内容,即使我们只更改了一个字段。
猜你喜欢
  • 2015-08-04
  • 2018-10-22
  • 1970-01-01
  • 2013-04-19
  • 2016-06-25
  • 1970-01-01
  • 1970-01-01
  • 2012-05-09
  • 1970-01-01
相关资源
最近更新 更多