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

Mysql在行上并发更新导致死锁,mysql,transactions,innodb,deadlock,database-deadlocks,Mysql,Transactions,Innodb,Deadlock,Database Deadlocks,使用mysql 5.7,存储引擎为innodb。我有一个存储产品信息的表。该表如下所示,productId上有唯一键 | Field | Type | Null | Key | Default | Extra | +-----------+--------------+------+-----+-------------------+-----------------------------+ | id

使用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              |                             |
+-----------+--------------+------+-----+-------------------+-----------------------------+
我有两个通过java应用程序连接到此mysql的操作:
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 for update语句正在等待对行执行X锁
3.事务1-Select for update语句尝试对行执行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-Select for update语句在行上使用X锁
2.事务2-Select for update语句正在等待对行执行X锁
3.事务1-Insert ignore语句尝试对行执行S锁,但事务1的X锁已在等待导致死锁的锁

因此,我希望知道如何处理并发更新并在不导致死锁的情况下将新事件(而不是行更新)插入表中
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锁的事务,这帮助我避免了死锁。

或者,重新应用被终止的事务。您确实需要做好这样做的准备,因为某些事情可能会导致死锁或其他对事务致命的错误。谢谢@RickJames的建议。是的,重新启动事务是一个选项。但是,这是一种反复出现的模式,上述解决方案解决了问题。总的来说,你对死锁有什么看法,是不是应该避免?有些死锁是可以避免的。我同意尽量避免它们。然而,我在这个论坛上发现许多人似乎认为所有的僵局都是可以避免的,并且花了太多的时间试图做到这一点。我很高兴你解决了自己的问题。