Recently, I found lots of deadlock errors in my application.
Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction: INSERT INTO `products`....
the code as below:
After user has been created, I will add some products to user. I don't understand why deadlock happened.
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
I didn't find the root cause, can anyone give me some advice? Thanks in advance!!!
Deadlock frequency can sometimes be reduced by ensuring that all applications access their common data in the same order - meaning, for example, that they access (and therefore lock) rows in Table A, followed by Table B, followed by Table C, and so on.
Deadlock happens when different concurrent transactions are unable to proceed because each one holds a lock that the other needs. Here is an example: Consider transaction #1 and transaction #2 both running at the same time.
Keep transactions small and short in duration to make them less prone to collision. Commit transactions immediately after making a set of related changes to make them less prone to collision. In particular, do not leave an interactive mysql session open for a long time with an uncommitted transaction.
My guess is that you are using InnoDB and probably doing concurrent insertions.
To trace and understand the cause, check out these articles:
One way to fix the issue is to retry like it is shown in the code below:
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
Or, there are some gems like transaction_retry to handle MySQL deadlocks.
This has saved me a lot of headaches: transaction_retry ruby gem.
From the README of the gem:
Retries database transaction on deadlock and transaction serialization errors. Supports MySQL, PostgreSQL, and SQLite.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With