ActiveRecord:避免has_many关系中的不一致

时间:2009-06-23 09:37:24

标签: ruby-on-rails ruby activerecord locking

假设我们在两个表之间有一个通常的M-M关系,例如:

用户 ---< users_tags > --- 标记

在这篇文章中,我只关注user_tags,tags的关系:我想避免删除链接的标签。只有未引用的标签才能销毁。

这样做的愚蠢方法是:

class Tag
  def before_destroy  
    unless self.user_tags.empty?
      raise "error"
    end
  end
end

但我认为检查user_tags.empty之间存在潜在的竞争条件?和实际的删除。

第二种方法可能是在检查是否有任何引用之前锁定整个 user_tags 表。

我能想到的第三种方式将涉及对代码的更改,从而创建实际的引用:

在users_tags中添加引用:

  1. 获取标记
  2. 锁定它(以避免同时破坏)
  3. 在users_tag中创建引用
  4. 提交
  5. 然后,before_destroy处理程序可以:

    1. self.lock!
    2. 检查是否有任何参考
    3. 摧毁自我
    4. 提交
    5. 有没有更好的方法来做到这一点?哪一个可靠/最好?我个人倾向于第二个,因为它只需要在before_destroy控制器中使用逻辑,但需要锁定整个表的成本。

      修改1:

      在尝试使用 LOCK TABLE 时,我意识到他们正在玩我的交易。当使用innodb时,您可以使用事务(及其锁定功能)或使用LOCK / UNLOCK表,两个世界的混合使事情变得更糟(LOCK / UNLOCK导致隐式提交,我错过了doc中的警告)。但这只是为了协议。

      编辑2(几周后):我再次与该问题斗争。所以我想再次强调不要使用LOCK TABLE

      我现在正在尝试在添加子项时对父对象(示例中的标记)使用 SHARE LOCK ),并为删除添加 FOR UPDATE 锁定。但我仍然想知道这是不是这样(在子表中锁定一个Rang以获取父表中的更新)。

      顺便说一下。我也意识到这个问题现在完全独立于rails :)。

1 个答案:

答案 0 :(得分:3)

避免锁定和检查的一种方法是简单地创建外键。尝试删除另一个表中引用的内容会产生SQL错误。

除此之外,你将不得不进行大量的偏执检查,以确保你没有找出任何必要的标签。

另一种方法是从不同的角度解决问题。例如,作为单个事务清除任何未使用的标记。例如:

DELETE FROM tags WHERE id NOT IN (SELECT DISTINCT(tag_id) FROM users_tags)

这样做的缺点是在模型级别上不执行before_destroy类型的行为,但这对您来说可能不是问题。