Mysql2::Error: 发现死锁,尝试重新启动事务:插入到...

8

最近,我在我的应用程序中发现了许多死锁错误。

Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction: INSERT INTO `products`....

以下是代码:
用户被创建后,我会向用户添加一些产品。我不理解为什么会发生死锁。
class User < ActiveRecord::Base
    after_create :add_products
    has_many :products, :dependent => :destroy

    def self.create_user
      User.create!(.......)
    end

    def add_products
      Product.add(self, "product_name", 10)
    end
    .....
end

class Product < ActiveRecord::Base
    belongs_to :user

    def self.add(user, product_name, amount)
       transaction do
         product = user.products.find_by_product_name(product_name)
         if product
            product.increment :amount, amount
            product.save!
         else
            product = self.create! user_id: user.id,
                                   product_name: product_name,
                                   amount: amount
         end
       end
       product
    end
end

我没有找到根本原因,有人能给我一些建议吗?先谢谢了!!!

在add方法中,为什么你又使用了self呢?请使用Product。 - Jyothu
你说得对,我已经修改了。 - pangpang
请参考以下链接,这会对您有所帮助:https://dev59.com/73E95IYBdhLWcg3wY85l - Sush
@liuzxc 那我的评论就是答案了吗? - Jyothu
@Jyoyhu:我不确定,你能解释一下为什么会导致死锁吗? - pangpang
https://www.honeybadger.io/blog/how-to-try-again-when-exceptions-happen-in-ruby/ - Abhi
2个回答

20

我猜想你正在使用InnoDB,而且可能正在进行并发插入操作。

要跟踪和了解原因,请参考以下文章:

修复问题的一种方法是像下面代码中所示进行重试:

def add_products
    retries = 0

    begin
        Product.add(self, "product_name", 10)
    rescue  ActiveRecord::StatementInvalid => ex
        if ex.message =~ /Deadlock found when trying to get lock/ #ex not e!!
            retries += 1   
            raise ex if retries > 3  ## max 3 retries 
            sleep 10
            retry
        else
            raise ex
        end
    end
end

或者,像transaction_retry这样的宝石可以处理MySQL死锁。


我有一个问题:保存和创建方法都包含在事务中,那么在“添加”方法中是否不需要添加事务? - pangpang
1
如果您有多个插入操作需要确保全部成功或中止,则需要使用事务。这完全取决于您的实现方式,但仅从您提供的代码来看,我认为不是必需的。 - Siva

4
这个 ruby gem: transaction_retry,真是解决了我很多烦恼。
以下是该gem的README简介:

在遇到死锁和事务序列化错误时,重试数据库事务。支持MySQL,PostgreSQL和SQLite。


1
抱歉,我知道您在这个网站上的声誉很高,但是...这个答案有点仅限于链接,而不是垃圾邮件。如果您能更详细地解释一下这将如何帮助回答问题,我们将不胜感激。 - Igbanam
@Yasky 这是一个宝石,完全处理了问题所询问的类型。在并发环境中,有时死锁是不可避免的,唯一的方法就是重试事务。这个宝石可以自动完成此任务。 - fguillen
有人有想法可以为这个案例创建测试吗?比如,我想模拟死锁并确保这个宝石实现可以解决它。 - Baran Yeni

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接