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

回答

3

closure_tree这里作者:

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

有趣的是,你建议使用with_advisory_lock!我刚刚为该closure_tree编写了该库,并且如果您使用的版本大于等于3.7.0,则已经有咨询锁来保护类级别#rebuild#find_or_create_by_path方法。

咨询锁(至少在MySQL和PostgreSQL中)的问题是它们不尊重事务边界 - 如果在锁被释放之前,锁持有调用者没有提交事务,其他连接将不会看到这些变化,当他们试图获得顾问锁,所以我们需要在这里小心。我们可能需要在层次结构表上添加表锁来进行写入操作,但这是最糟糕的情况。

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

+1

此问题已得到解决。请升级到v3.7.3。 – mrm

+0

你能看看我的问题在http://stackoverflow.com/questions/41388300/use-ruby-closure-tree-gem-without-rails? – peter

2

您需要考虑一个事务如何与另一个事务协同工作。你最好打赌是确保你先阅读东西(选择),然后再写SQL。还要确保写入的SQL以相同的顺序使用这些表 - 即在两种情况下写入表A然后写入B.这将防止需要锁定,同时由另一个事务持有,而另一个事务需要另一个事务进行锁定。

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