使用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看起来很有希望。有什么想法吗?
答案 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.这将防止在需要另一个事务锁定的另一个事务持有时需要锁定。
或者,您可以检测到死锁并采取适当的措施。我建议首先避免使用它们。