并发编辑闭包树层次结构时的死锁

时间:2013-02-07 14:48:27

标签: mysql ruby-on-rails-3 deadlock sti transitive-closure-table

使用closure_tree同时操作具有分层结构上的公共属性的一组模型时,如何避免数据库死锁?

它们具有以下特征:

发出#append/prepend_sibling

Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction:
  UPDATE `elements` SET `sort_order` = `sort_order` + 1 WHERE (`parent_id` = 28035 AND `sort_order` >= 1)

Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction:
  UPDATE `elements` SET `sort_order` = `sort_order` - 1 WHERE (`parent_id` = 21168 AND `sort_order` <= -1)

重建闭包表时

Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction:
  DELETE FROM `element_hierarchies`
   WHERE descendant_id IN (
     SELECT DISTINCT descendant_id
     FROM ( SELECT descendant_id
       FROM `element_hierarchies`
       WHERE ancestor_id = 16332
     ) AS x )
     OR descendant_id = 16332

Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction: 
  INSERT INTO `element_hierarchies` (`ancestor_id`, `descendant_id`, `generations`) VALUES (30910, 30910, 0)

with_advisory_lock看起来很有希望。有什么想法吗?

2 个答案:

答案 0 :(得分:3)

closure_tree的作者:

先生。治愈的建议通常是正确的 - 您应该以相同的顺序获取表锁以防止死锁。但是,在这种情况下,死锁是由于层次结构表中的行级锁定。

有趣的是,您建议使用with_advisory_lock!我刚刚为closure_tree编写了该库,如果你使用的是版本&gt; = 3.7.0,那么已经存在保护类级#rebuild#find_or_create_by_path方法的咨询锁。

咨询锁(至少使用MySQL和PostgreSQL)的问题在于它们不尊重事务边界 - 如果锁定调用程序在锁定释放之前未提交其事务,则其他连接将不会看到当他们试图获得咨询锁时会发生变化,所以我们需要在这里小心。我们可能需要在层次结构表上为任何写入添加表锁,但这是最坏的情况。

我已经打开issue 41,我们可以在那里跟踪它。首先要做的是在并行测试中可靠地重现死锁。我们已经为#rebuild#find_or_create_by_path进行了测试。

答案 1 :(得分:2)

您需要考虑一个事务如何与另一个事务一起工作。最好的办法是确保首先执行读取操作(选择),然后再编写SQL。还要确保写SQL以相同的顺序使用表 - 即在两种情况下写入表A然后写入B.这将防止在需要另一个事务锁定的另一个事务持有时需要锁定。

或者,您可以检测到死锁并采取适当的措施。我建议首先避免使用它们。