续集(Ruby),如何以安全的方式增加和使用DB计数器?
我找到了4种“正确”的方法:
- 在ActiveRecord的备忘单中,用户替换ActiveRecord的
increment
,increment_counter
应该是album.values[:column] -= 1 # or += 1 for increment
和album.update(:counter_name=>Sequel.+(:counter_name, 1))
- 在SO解决方案中,
update_sql
建议用于相同的效果s[:query_volume].update_sql(:queries => Sequel.expr(3) + :queries)
- 在随机线程中,我发现了这个数据集
dataset.update_sql(:exp => 'exp + 10'.lit)
- 在用于更新的Sequels API文档中,我找到了这个解决方案
http://sequel.jeremyevans.net/rdoc/classes/Sequel/Dataset.html#method-i-update
然而,没有一个解决方案实际更新值并以安全,primefaces的方式返回结果。
基于“添加值然后保存”的解决方案应该在多处理环境中以不确定的方式失败,从而导致以下错误:
- 专辑的计数器是0
- 线程A和线程B都获取
album
- 线程A和线程B都增加了散列/模型/ etc中的值
- 线程A和线程B都将计数器更新为相同的值
- 结果:A和B都将计数器设置为1并使用计数器值1
Sequel.expr
, Sequel.expr
和Sequel.+
实际上并没有返回一个值,而是一个Sequel::SQL::NumericExpression
和(afaik)你没有办法解决另一个DB往返问题,这意味着可以发生:
- 专辑的计数器是0
- 线程A和B都增加值,值增加2
- 线程A和B都从DB中获取行
- 结果:A和B都将计数器设置为2并使用计数器值2
因此,如果没有编写自定义锁定代码,那么解决方案是什么? 如果没有,没有编写自定义锁定代码:)最好的方法是什么?
更新1
我一般不满意答案说我想要太多的生活,因为1回答建议:)
专辑只是文档中的一个例子。
想象一下,例如,您在电子商务POS上有一个交易柜台,它可以在不同的主机上同时接受2个交易,并且您需要在24小时内发送一个唯一的整数计数器(称为systan),发送2具有相同systan和1的trx将被拒绝,或者更糟糕的是,计数中的间隙被警告(因为它们暗示“缺少事务”),因此不可能使用DB的ID值。
一个不太严重的例子,但与我的用例更相关,几个文件导出在后台worker中同时触发,每个文件目的地都有自己的计数器。 计数器中的间隙被警告,工作人员在不同的主机上(因此互斥体没有用)。 我有一种感觉,我很快就会解决更严重的问题。
数据库序列也不好,因为它意味着在添加每个终端时进行DDL,我们在这里谈论1000。 即使在我不太严重的用例中,门户网站上的DDLing操作仍然是PITA,甚至可能根据下面的缓存方案不起作用(由于ActiveRecord
和Sequel
– 在我的情况下我使用两者 – 可能需要重启服务器注册商家)。
Redis可以做到这一点,但是当你坐在符合ACID标准的数据库上时,为计数器添加另一个基础架构组件似乎很疯狂。
如果你正在使用PostgreSQL,你可以使用UPDATE RETURNING: DB[:table].returning(:counter).update(:counter => Sequel.expr(1) + :counter)
但是,如果不支持UPDATE RETURNING或类似的东西,则无法在返回递增值的同时以primefaces方式递增。
答案是 – 在multithreading环境中,不要使用DB计数器。 面对这种困境时:
- 如果我需要一个唯一的整数计数器,请使用一个线程安全的计数器生成器,它可以在线程需要时包装计数器。 这可以是一个简单的整数,也可以像Twitter Snowflake一样复杂。
- 如果我需要一个唯一的标识符,我会使用像uuid这样的东西
在您需要计算专辑的特定情况下 – 您是否有理由在数据库中而不是作为模型上的派生字段?
更新1:
鉴于您正在处理与多个主机上的工作人员近似文件导出的内容,您需要提前分配ID(即,将具有作业的工作者和来自单个规范来源的下一个可用ID)或让工作者呼叫中央服务,以先到先得的方式分配交易ID。
我想不出另一种方法。 我从未使用过POS系统,但我所使用的电信网络配置系统通常使用单个事务生成器服务,该服务适当地命名为id。