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

我找到了4种“正确”的方法:

  1. 在ActiveRecord的备忘单中,用户替换ActiveRecord的incrementincrement_counter应该是album.values[:column] -= 1 # or += 1 for incrementalbum.update(:counter_name=>Sequel.+(:counter_name, 1))
  2. 在SO解决方案中, update_sql建议用于相同的效果s[:query_volume].update_sql(:queries => Sequel.expr(3) + :queries)
  3. 在随机线程中,我发现了这个数据集dataset.update_sql(:exp => 'exp + 10'.lit)
  4. 在用于更新的Sequels API文档中,我找到了这个解决方案http://sequel.jeremyevans.net/rdoc/classes/Sequel/Dataset.html#method-i-update

然而,没有一个解决方案实际更新值并以安全,primefaces的方式返回结果。

基于“添加值然后保存”的解决方案应该在多处理环境中以不确定的方式失败,从而导致以下错误:

  1. 专辑的计数器是0
  2. 线程A和线程B都获取album
  3. 线程A和线程B都增加了散列/模型/ etc中的值
  4. 线程A和线程B都将计数器更新为相同的值
  5. 结果:A和B都将计数器设置为1并使用计数器值1

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

  1. 专辑的计数器是0
  2. 线程A和B都增加值,值增加2
  3. 线程A和B都从DB中获取行
  4. 结果:A和B都将计数器设置为2并使用计数器值2

因此,如果没有编写自定义锁定代码,那么解决方案是什么? 如果没有,没有编写自定义锁定代码:)最好的方法是什么?

更新1

我一般不满意答案说我想要太多的生活,因为1回答建议:)

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

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

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

数据库序列也不好,因为它意味着在添加每个终端时进行DDL,我们在这里谈论1000。 即使在我不太严重的用例中,门户网站上的DDLing操作仍然是PITA,甚至可能根据下面的缓存方案不起作用(由于ActiveRecordSequel – 在我的情况下我使用两者 – 可能需要重启服务器注册商家)。

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

如果你正在使用PostgreSQL,你可以使用UPDATE RETURNING: DB[:table].returning(:counter).update(:counter => Sequel.expr(1) + :counter)

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

答案是 – 在multithreading环境中,不要使用DB计数器。 面对这种困境时:

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

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

更新1:

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

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