导致死锁的行上的Mysql并发更新

时间:2017-04-25 19:06:40

标签: mysql transactions innodb deadlock database-deadlocks

将mysql 5.7与存储引擎一起用作innodb。我有一个存储产品信息的表。该表在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 - 插入ignore语句对该行进行了S锁定。
2.事务2 - 选择更新语句正在等待对该行进行X锁定。
3.事务1 - 选择更新语句尝试对该行进行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.事务1 - 选择更新语句对行进行X锁定。
2.事务2 - 选择更新语句正在等待对该行进行X锁定。
3.事务1 - 插入ignore语句尝试对该行进行S锁定,但事务1的X锁定已经在等待导致死锁的锁定

所以,我想知道如何处理并发更新并将新事件(而不是行更新)插入到我的表中而不会导致死锁。
1.锁定顺序应该是什么?
2.如何确保并发更新和新行插入在没有死锁的情况下工作。

任何帮助将不胜感激:)

1 个答案:

答案 0 :(得分:1)

我设法在一些实验后解决了它,核心问题是S的序列,然后在一个事务中采用X锁,而在另一个事务中采用X锁。基本上,在开始时采取的S锁导致所有情况都有死锁。
因此,我将事务外部的insert ignore语句作为第一个语句移动。该事务现在只接受X锁定,这意味着其中一个事务在另一个事务上等待X锁定。

Event1:插入新活动

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锁的事务,这有助于我避免死锁。