Mysql在行上并发更新导致死锁
使用mysql 5.7,存储引擎为innodb。我有一个存储产品信息的表。该表如下所示,productId上有唯一键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
| 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的建议。是的,重新启动事务是一个选项。但是,这是一种反复出现的模式,上述解决方案解决了问题。总的来说,你对死锁有什么看法,是不是应该避免?有些死锁是可以避免的。我同意尽量避免它们。然而,我在这个论坛上发现许多人似乎认为所有的僵局都是可以避免的,并且花了太多的时间试图做到这一点。我很高兴你解决了自己的问题。