Mysql 一行并发更新导致死锁
Mysql concurrent updates on a row leading to deadlock
使用 mysql 5.7 和存储引擎作为 innodb。我有一个存储产品信息的 table。 table 看起来像这样,在 productId
上有唯一键
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+-------------------+-----------------------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| productId | varchar(50) | NO | UNI | NULL | |
| seller | varchar(100) | NO | MUL | NULL | |
| updatedAt | timestamp | NO | MUL | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| status | varchar(100) | NO | MUL | NULL | |
| data | longtext | NO | | NULL | |
+-----------+--------------+------+-----+-------------------+-----------------------------+
我通过连接到此 mysql 的 java 应用程序进行了两项操作:
1. 如果 productId 的版本高于现有事件,则需要插入新传入事件(包含有关产品更改的信息)。该版本在我的数据列
中存储为 json blob
2. 更新 productId 的行以更改状态。
我的隔离级别是读提交。
我尝试了两种方法,但都导致了死锁:
方法 1:
Transaction1 starts
Insert ignore into products where productId='X' values(); // Takes a S lock on the row
select * from products where productId='X' for update ; // Take a X lock on the row to prevent new writes and compare the incoming event with the current event
Insert into products values on duplicate key update values // insert into row and on duplicate key update values
commit
并发更新将打开另一个事务:
Transaction2 starts
select * from products where productId='X' for update ; // Take a X lock on the row to prevent new writes and compare the incoming event with the current event
Insert into products values on duplicate key update values // insert into row and on duplicate key update values
commit;
在以下情况下会导致死锁:
1. 事务 1 - Insert ignore 语句在该行上取得了 S 锁。
2. 更新语句的事务 2 - Select 正在等待获取行上的 X 锁。
3. Transaction 1 - Select for update statement 尝试在行上获取 X 锁。
这会导致死锁,因为事务 1 持有 S 锁,而事务 2 正在等待获取 X 锁,当事务 1 尝试获取 X 锁时,会导致死锁。
方法 2:
Transaction 1 starts:
select * from products where productId='X' for update ; // If a row exists then it will be locked else I know it does not exist
Insert ignore into products where productId='X' values();
commit
Transaction 2 starts:
select * from products where productId='X' for update ; // If a row exists then it will be locked else I know it does not exist
commit
在以下情况下会导致死锁:
1. Transaction 1 - Select for update statement 在行上获取 X 锁。
2. 更新语句的事务 2 - Select 正在等待获取行上的 X 锁。
3. 事务 1 - Insert ignore 语句试图在行上获取 S 锁,但是事务 1 的 X 锁已经在等待导致死锁的锁
所以,我想知道如何处理并发更新并将新事件(而不是行更新)插入我的 table 而不会导致死锁。
1.锁定顺序应该是什么?
2. 如何确保并发更新和新行插入没有死锁。
任何帮助将不胜感激:)
经过一些实验我设法解决了它,核心问题是一个事务中S和X锁的顺序,另一个事务中的X锁。基本上,一开始使用的 S 锁导致所有情况都出现死锁。
因此,我将 insert ignore 语句移到了事务之外作为第一条语句。该事务现在只占用 X 锁,这意味着一个事务等待另一个占用 X 锁。
事件 1:插入新事件
result = Insert ignore into products where productId='X' values();
if result == null
return
end
Transaction start
select * from products where productId='X' for update ; // Take a X lock on the row to prevent new writes and compare the incoming event with the current event
Insert into products values on duplicate key update values // insert into row and on duplicate key update values
commit
活动 2:更新现有活动
Transaction start
select * from products where productId='X' for update ; // If a row exists then it will be locked else I know it does not exist
commit
所以,这两个事件都有只竞争 X 锁的事务,这帮助我避免了死锁。
使用 mysql 5.7 和存储引擎作为 innodb。我有一个存储产品信息的 table。 table 看起来像这样,在 productId
上有唯一键| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+-------------------+-----------------------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| productId | varchar(50) | NO | UNI | NULL | |
| seller | varchar(100) | NO | MUL | NULL | |
| updatedAt | timestamp | NO | MUL | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| status | varchar(100) | NO | MUL | NULL | |
| data | longtext | NO | | NULL | |
+-----------+--------------+------+-----+-------------------+-----------------------------+
我通过连接到此 mysql 的 java 应用程序进行了两项操作:
1. 如果 productId 的版本高于现有事件,则需要插入新传入事件(包含有关产品更改的信息)。该版本在我的数据列
中存储为 json blob
2. 更新 productId 的行以更改状态。
我的隔离级别是读提交。 我尝试了两种方法,但都导致了死锁:
方法 1:
Transaction1 starts
Insert ignore into products where productId='X' values(); // Takes a S lock on the row
select * from products where productId='X' for update ; // Take a X lock on the row to prevent new writes and compare the incoming event with the current event
Insert into products values on duplicate key update values // insert into row and on duplicate key update values
commit
并发更新将打开另一个事务:
Transaction2 starts
select * from products where productId='X' for update ; // Take a X lock on the row to prevent new writes and compare the incoming event with the current event
Insert into products values on duplicate key update values // insert into row and on duplicate key update values
commit;
在以下情况下会导致死锁:
1. 事务 1 - Insert ignore 语句在该行上取得了 S 锁。
2. 更新语句的事务 2 - Select 正在等待获取行上的 X 锁。
3. Transaction 1 - Select for update statement 尝试在行上获取 X 锁。
这会导致死锁,因为事务 1 持有 S 锁,而事务 2 正在等待获取 X 锁,当事务 1 尝试获取 X 锁时,会导致死锁。
方法 2:
Transaction 1 starts:
select * from products where productId='X' for update ; // If a row exists then it will be locked else I know it does not exist
Insert ignore into products where productId='X' values();
commit
Transaction 2 starts:
select * from products where productId='X' for update ; // If a row exists then it will be locked else I know it does not exist
commit
在以下情况下会导致死锁:
1. Transaction 1 - Select for update statement 在行上获取 X 锁。
2. 更新语句的事务 2 - Select 正在等待获取行上的 X 锁。
3. 事务 1 - Insert ignore 语句试图在行上获取 S 锁,但是事务 1 的 X 锁已经在等待导致死锁的锁
所以,我想知道如何处理并发更新并将新事件(而不是行更新)插入我的 table 而不会导致死锁。
1.锁定顺序应该是什么?
2. 如何确保并发更新和新行插入没有死锁。
任何帮助将不胜感激:)
经过一些实验我设法解决了它,核心问题是一个事务中S和X锁的顺序,另一个事务中的X锁。基本上,一开始使用的 S 锁导致所有情况都出现死锁。
因此,我将 insert ignore 语句移到了事务之外作为第一条语句。该事务现在只占用 X 锁,这意味着一个事务等待另一个占用 X 锁。
事件 1:插入新事件
result = Insert ignore into products where productId='X' values();
if result == null
return
end
Transaction start
select * from products where productId='X' for update ; // Take a X lock on the row to prevent new writes and compare the incoming event with the current event
Insert into products values on duplicate key update values // insert into row and on duplicate key update values
commit
活动 2:更新现有活动
Transaction start
select * from products where productId='X' for update ; // If a row exists then it will be locked else I know it does not exist
commit
所以,这两个事件都有只竞争 X 锁的事务,这帮助我避免了死锁。