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

Sequel(Ruby),如何以安全的方式递增和使用DB计数器?,ruby,atomic,sequel,Ruby,Atomic,Sequel,我找到了4种“正确”的方法: 在ActiveRecord的increment和increment\u计数器的替代品中,increment和album.update的值[:column]=1或+=1(:counter\u name=>Sequel.+(:counter\u name,1)) 在update\u中,建议使用sql以实现相同的效果s[:query\u volume]。update\u sql(:querys=>Sequel.expr(3)+:querys) 在一个数据库中,我找到了这个

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

  • 在ActiveRecord的
    increment
    increment\u计数器
    的替代品中,increment和
    album.update的值[:column]=1或+=1(:counter\u name=>Sequel.+(:counter\u name,1))
  • update\u中,建议使用sql
    以实现相同的效果
    s[:query\u volume]。update\u sql(:querys=>Sequel.expr(3)+:querys)
  • 在一个数据库中,我找到了这个
    dataset.update\u sql(:exp=>'exp+10'.lit)
  • 在中,我找到了这个解决方案
    http://sequel.jeremyevans.net/rdoc/classes/Sequel/Dataset.html#method-i-update
  • 然而,没有一种解决方案能够以安全、原子的方式更新值并返回结果

    基于“添加值,然后保存”的解决方案在多处理环境中可能会出现不确定的故障,并导致以下错误:

  • 相册的计数器为0
  • 线程A和线程B都获取相册
  • 线程A和线程B都会增加hash/model/etc中的值
  • 线程A和线程B都将计数器更新为相同的值
  • 结果:a和B都将计数器设置为1,并使用计数器值1
  • 另一方面,
    Sequel.expr
    Sequel.+
    实际上并不返回值,而是返回一个
    Sequel::SQL::NumericExpression
    和(afaik)如果不进行另一次DB往返,您将无法得到它,这意味着可能发生这种情况:

  • 相册的计数器为0
  • 线程A和B都增加值,值增加2
  • 线程A和B都从数据库中获取行
  • 结果:a和B都将计数器设置为2,并使用计数器值2
  • 那么,除了编写自定义锁定代码,解决方案是什么?如果没有,除了编写自定义锁定代码:)最好的方法是什么

    更新1 我通常不喜欢回答说我想要太多的生活,正如第一个答案所示:)

    相册只是文档中的一个例子

    例如,假设您在一个电子商务POS机上有一个交易计数器,它可以在不同主机上同时接受2笔交易,并且您需要使用24小时内唯一的整数计数器(称为systan)将其发送到银行,则使用相同systan发送2笔trx,1笔将被拒绝,或者更糟的是,会提醒计数差距(因为它们提示“缺少事务”),所以不可能使用DB的ID值

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

    DB序列也不好,因为这意味着在添加每个终端时都要进行DDL,我们这里说的是1000。即使在我的服务器较少的用例中,在web门户上进行DDL操作仍然是一个PITA,甚至可能无法工作,这取决于下面的缓存方案(由于实施了
    ActiveRecord
    Sequel
    ——在我的情况下,我同时使用了这两种方法——可能需要重新启动服务器才能注册商户)


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

    答案是-在多线程环境中,不要使用DB计数器。当面临此难题时:

  • 如果我需要一个唯一的整数计数器,请使用线程安全计数器生成器,根据线程的需要将计数器打包。这可以是一个简单的整数,也可以是类似于Twitter雪花的生成器这样更复杂的生成器
  • 如果我需要一个唯一的标识符,我会使用uuid之类的东西
  • 在您的特定情况下,您需要一个相册计数-您需要在数据库中而不是在模型上作为派生字段使用它的原因是什么

    更新1:

    考虑到您正在处理与多台主机上的工作程序的文件导出类似的事情,您需要提前分配id(即,为工作程序分配一个作业和来自单个规范源的下一个可用id)或者让工作人员调用一个中央服务,该服务按照先到先得的原则分配事务ID


    我想不出另一种方法。我从未使用过POS系统,但我所使用的电信网络供应系统通常使用一个事务生成器服务,该服务根据需要命名ID。

    如果您使用的是PostgreSQL,则可以使用更新返回:
    DB[:table]。返回(:counter)。更新(:counter=>Sequel.expr(1)+:counter)


    但是,如果不支持更新返回或类似操作,就无法在返回递增值的同时自动递增。

    @bbozo回应。希望有经验的人能在这里帮助您:)这样“安全地维护许多计数器”太疯狂了一直是2016年的难题哇,你好:D谢谢:D在我的情况下就可以了:)如果有一个通用的方法就好了。