锁定导轨上的行更新以避免冲突。 (Postgres后端)

时间:2017-09-29 17:17:06

标签: ruby-on-rails rails-activerecord rails-models

所以我的模型对象上有一个方法,当行中的二进制字段从null更新为true时,该方法会创建一个唯一的序列号。它实现如下:

class AnswerHeader < ApplicationRecord
  before_save :update_survey_complete_sequence, if: :survey_complete_changed?

  def update_survey_complete_sequence
    maxval =AnswerHeader.maximum('survey_complete_sequence')
    self.survey_complete_sequence=maxval+1
  end
end

我的问题是我需要锁定什么才能同时更新两行,而不是两行具有相同的survey_complete_sequence?

如果可以锁定单行而不是整个表,那么这是一个很好的,因为这是用户经常访问的表。

5 个答案:

答案 0 :(得分:1)

如果您正在使用postgress,也许Sequenced可以帮助您而无需在数据库级别定义序列。

Survey_complete_sequence应该是增量的吗?如果没有,也许随机化一个bigint?

答案 1 :(得分:1)

如果你想在应用程序逻辑本身处理它,而不是让数据库处理这个。您可以使用rails with_lock函数来创建事务在所选行上获取行级数据库锁(在您的情况下为单行)。

答案 2 :(得分:1)

您可能不想锁定该表,即使您锁定了当前正在更新该行的行,您的maxval将基于另一个更新来读取和生成序列#。

除非你有一个巨大的表格,并且每毫秒都有很多更新(大约数千),否则这在现实生活中不应成为问题。但是如果这个想法困扰你,你可以继续在表格上添加一个独特的索引,在&#34; survey_complete_sequence&#34;柱。数据库错误将传播到您可以在应用程序中处理的Rails异常。

答案 3 :(得分:1)

我相信你应该给一些咨询锁。它确保同一个代码块不会同时在两台机器上执行,同时仍然保持该表对其他业务开放。

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

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

$model->value = isset( $value ) ? $value : null;

https://github.com/ClosureTree/with_advisory_lock

它不适用于SQLite。

答案 4 :(得分:1)

您需要锁定的内容

在您的情况下,您必须锁定包含最大survey_complete_sequence的行,因为这是每个查询在获取您需要的值时将查找的行。

maxval =AnswerHeader.maximum('survey_complete_sequence')

是否可以锁定单行而不是整个表格

您的方案没有此类特定锁定。但是你可以使用Postgresql SELECT FOR UPDATE row-level locking

  

在实际上获取行上的独占行级锁定   修改行,使用SELECT FOR UPDATE选择行。

你可以在rails中使用pessimistic locking并指定你将使用哪个锁。

  

调用锁定(&#39;某些锁定子句&#39;)以使用特定于数据库的锁定   你自己的条款,如锁定模式&#39;或者&#39;更新NOWAIT&#39;

以下是如何从rails官方指南本身实现这一目标的一个例子

Item.transaction do
  i = Item.lock("LOCK IN SHARE MODE").find(1)
  ...
end
  

使用锁的关系通常包含在事务中以防止死锁条件。

所以你需要做的是 -

  1. SELECT FOR UPDATE锁定应用于包含maximum('survey_complete_sequence')
  2. 的行
  3. 从该行获取所需的值
  4. 使用收到的值更新您的AnswerHeader