Rails ActiveRecord - 如何锁定表格进行读取?

Nie*_* B. 14 sql postgresql activerecord locking ruby-on-rails

我有一些Rails ActiveRecord代码,如下所示:

new_account_number = Model.maximum(:account_number)
# Some processing that usually involves incrementing
# the new account number by one.
Model.create(foo: 12, bar: 34, account_number: new_account_number)
Run Code Online (Sandbox Code Playgroud)

这段代码本身很好用,但我有一些由DelayedJob工作人员处理的后台作业.有两个工作者,如果他们都开始处理一批处理此代码的作业,他们最终会创建Model具有相同account_number的新记录,因为找到最大值和创建具有更高帐户的新记录之间的延迟数.

现在,我已经通过在数据库级别向模型表添加唯一性约束来解决它,然后通过在该约束触发异常的情况下重新选择最大值来重试.

然而,感觉就像一个黑客.

在数据库级别向account_number列添加自动递增不是一种选择,因为account_number分配不仅仅需要递增.

理想情况下,我想锁定有问题的表进行读取,因此在完成之前,没有其他人可以对表执行最大选择查询.但是,我不知道该怎么做.我正在使用Postgresql.

Max*_*ace 14

基于ActiveRecord :: Locking文档,看起来Rails没有为表级锁提供内置API.

但您仍然可以使用原始SQL执行此操作.对于Postgres来说,这看起来像

ActiveRecord::Base.transaction do
  ActiveRecord::Base.connection.execute('LOCK table_name IN ACCESS EXCLUSIVE MODE')
  ...
end
Run Code Online (Sandbox Code Playgroud)

锁必须在事务中获取,并在事务结束后自动释放.

请注意,您在此处使用的SQL将根据您的数据库而有所不同.


kas*_*rnj 9

关于如何锁定整个表已有答案,但我相信你应该尽量避免这种情况.相反,我相信你应该给一些咨询锁.它确保同时在两台机器上执行相同的代码块,同时仍然保持表对其他业务开放.

它仍然使用数据库,但它不会锁定您的表.

您可以像这样使用名为"with_advisory_lock"的gem:

Model.with_advisory_lock("ADVISORY_LOCK_NAME") do
  # Your code
end
Run Code Online (Sandbox Code Playgroud)

https://github.com/ClosureTree/with_advisory_lock

它不适用于SQLite.


Nik*_*kov 6

设置唯一约束不是黑客.这是使您的数据保持一致的因素.顺便提一下,你还有一些选择:

  1. 使用SELECT FOR UPDATE或PostreSQL的Advisory Locks锁定一些数据库资源(例如,它可能是一个唯一的记录)(参见文档).

  2. 使用序列(docs).

两种方法之间的主要区别是#1不允许在您的数字中存在间隙,因为其他会话将等待事务提交而#2允许.