4

当使用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看起来很有希望。有什么想法吗?

4

2 回答 2

3

这里的closure_tree的作者:

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

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

咨询锁(至少在 MySQL 和 PostgreSQL 中)的问题在于它们不尊重事务边界——如果持有锁的调用者在释放锁之前没有提交他们的事务,那么其他连接将不会看到这些更改。尝试获取咨询锁,所以我们需要在这里小心。我们可能需要在层次表上为任何写入添加表锁,但这是最坏的情况。

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

于 2013-02-10T18:09:22.517 回答
2

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

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

于 2013-02-07T15:19:04.947 回答