续集(Ruby),如何以安全的方式增加和使用数据库计数器?

时间:2016-08-02 06:48:30

标签: ruby atomic sequel

我发现4"正确"方法:

  1. the cheat sheet for ActiveRecord users替代ActiveRecord的incrementincrement_counter应该是album.values[:column] -= 1 # or += 1 for incrementalbum.update(:counter_name=>Sequel.+(:counter_name, 1))
  2. 建议使用SO solution update_sql同样效果s[:query_volume].update_sql(:queries => Sequel.expr(3) + :queries)
  3. random thread我找到了这个dataset.update_sql(:exp => 'exp + 10'.lit)
  4. the Sequels API docs for update我找到了此解决方案http://sequel.jeremyevans.net/rdoc/classes/Sequel/Dataset.html#method-i-update
  5. 但是没有一个解决方案实际更新值并以安全,原子的方式返回结果。

    解决方案基于"添加值然后保存"应该,afaik,在多处理环境中不确定地失败,导致错误,例如:

    1. 专辑的计数器为0
    2. 主题A和主题B都获取album
    3. 线程A和线程B都增加了hash / model / etc
    4. 中的值
    5. 线程A和线程B都将计数器更新为相同的值
    6. 结果:A和B都将计数器设置为1并使用计数器值1
    7. 另一方面,

      Sequel.exprSequel.+实际上并没有返回一个值,但是Sequel::SQL::NumericExpression和(afaik)你没有办法解决这个问题另一个DB往返,这意味着可能发生这种情况:

      1. 专辑的计数器为0
      2. 线程A和B都增加值,值增加2
      3. 线程A和B都从DB
      4. 中获取行
      5. 结果:A和B都将计数器设置为2并使用计数器值2
      6. 因此,如果没有编写自定义锁定代码,解决方案是什么?如果没有,没有编写自定义锁定代码:)最好的方法是什么?

        更新1

        我一般不满意答案,说我想要太多的生活,正如一个答案所暗示的那样:)

        专辑只是文档的一个例子。

        想象一下,例如,您在电子商务POS上有一个交易柜台,它可以在不同的主机上同时接受2个交易,并且您需要在24小时内使用整数计数器发送它们(称为systan) ,发送2 trx与同一个systan,1将被拒绝,或者更糟糕的是,计数中的间隙被警告(因为他们提示"缺少事务")因此它不可能使用DB&# 39; s ID值。

        一个不太严重的例子,但与我的用例更相关,在后台worker中同时触发了几个文件导出,每个文件目的地都有自己的计数器。计数器中的间隙被警告,工作人员在不同的主机上(因此互斥体没有用)。而且我有一种感觉,我很快就会解决更严重的问题。

        数据库序列也不好,因为它意味着在添加每个终端时都要做DDL,而我们在这里说1000。即使在我不太严重的用例中,门户网站上的DDLing操作仍然是PITA,甚至可能无法工作,具体取决于下面的缓存方案(由于ActiveRecordSequel的实施 - 在我的情况下我使用两者 - 可能只需要重新启动服务器来注册商家。

        Redis可以做到这一点,但是当您坐在符合ACID标准的数据库上时,为计数器添加另一个基础架构组件似乎很麻烦。

2 个答案:

答案 0 :(得分:6)

如果您使用的是PostgreSQL,则可以使用UPDATE RETURNING:DB[:table].returning(:counter).update(:counter => Sequel.expr(1) + :counter)

但是,如果不支持UPDATE RETURNING或类似的东西,则无法在返回递增值的同时以原子方式递增。

答案 1 :(得分:1)

答案是 - 在多线程环境中,不要使用数据库计数器。面对这种困境时:

  1. 如果我需要一个唯一的整数计数器,请使用一个线程安全的计数器生成器,它会在线程需要时包装计数器。这可以是一个简单的整数,也可以像Twitter Snowflake一样更复杂。
  2. 如果我需要一个唯一的标识符,我会使用像uuid
  3. 这样的东西

    在您需要计算专辑的特定情况下 - 您是否有理由在数据库上而不是模型上的派生字段?

    更新1:

    鉴于您正在处理与多个主机上的工作人员近似文件导出的内容,您需要提前分配ID(即,将具有作业的工作人员和来自单个规范来源的下一个可用ID)或让工作人员呼叫中央服务,以先到先得的方式分配交易ID。

    我想不出另一种方法。我从未使用过POS系统,但我所使用的电信网络配置系统通常使用单个事务生成器服务,并在适当时命名为id。