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

时间:2014-07-05 14:27:52

标签: sql ruby-on-rails postgresql activerecord locking

我有一些看起来像这样的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)

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

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

但感觉就像是黑客。

将数据库级别的自动递增添加到account_number列不是一种选择,因为account_number分配不仅仅需要递增。

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

6 个答案:

答案 0 :(得分:13)

基于the ActiveRecord::Locking docs,看起来Rails没有为表级锁提供内置API。

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

ActiveRecord::Base.transaction do
  ActiveRecord::Base.connection.execute('LOCK table_name IN ACCESS EXCLUSIVE MODE')
  ...
end

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

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

答案 1 :(得分:7)

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

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

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

Model.with_advisory_lock("ADVISORY_LOCK_NAME") do
  # Your code
end

https://github.com/ClosureTree/with_advisory_lock

它不适用于SQLite。

答案 2 :(得分:6)

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

  1. 使用锁定某些数据库资源(例如,它可能是唯一的记录) SELECT FOR UPDATE或PostreSQL的咨询锁(见docs)。

  2. 使用序列(docs)。

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

答案 3 :(得分:0)

您可以使用ActiveRecord::Locking::Pessimistic中的lock方法。

答案 4 :(得分:-1)

嗯,从技术上来说,在访问表之前锁定表或总是锁定另一个表的记录是一样的。

所以你可能有另一个表有一个最大记录的表,在你要锁定的表中读/写之前,总是用http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html锁定记录:

LockTable.last.with_lock do
  // the things that needed for your table
end

答案 5 :(得分:-1)

您无需锁定大厅桌面,即可一次锁定一个代码。锁定一个完整的表会导致性能问题。你可以使用“with_lock”方法一直锁定一个相同的行。这样代码就完全受到保护。不需要额外的宝石。它还会创建一个交易。像这样:  m = Model.order(:id).first m.with_lock do #aquire lock #some code here for a single process at a time
end #release lock