数据库遇到并发更新两个事务导致的死锁问题。
LATEST DETECTED DEADLOCK
------------------------
2019-04-18 15:54:09 0x7f85cff7e700
*** (1) TRANSACTION:
TRANSACTION 70678199277, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 137 lock struct(s), heap size 24784, 689 row lock(s), undo log entries 10
MySQL thread id 6314744, OS thread handle 140210780473088, query id 1764862374 10.32.94.170 m_pr_d090 Searching rows for update
UPDATE table1 SET status =1 WHERE c_Id = 24671 and d_Id =1247910
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 12918 page no 4088 n bits 688 index idx_cinemaid_dcardid_status of table `mr`.`table1` trx id 70678199277 lock_mode X waiting
*** (2) TRANSACTION:
TRANSACTION 70678199289, ACTIVE 0 sec updating or deleting
mysql tables in use 1, locked 1
144 lock struct(s), heap size 24784, 721 row lock(s), undo log entries 13
MySQL thread id 6313652, OS thread handle 140212696508160, query id 1764862806 10.4.189.142 m_pr_d090 updating
UPDATE table1 SET status =1 WHERE c_Id = 24670 and d_Id =1247910
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 12918 page no 4088 n bits 688 index idx_cinemaid_dcardid_status of table `mr`.`table1` trx id 70678199289 lock_mode X
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 12918 page no 4088 n bits 688 index idx_cinemaid_dcardid_status of table `mr`.`table1` trx id 70678199289 lock_mode X locks gap before rec insert intention waiting
cId,d_Id是联合索引,非唯一
session1 UPDATE table1 SET status = 1 WHERE c_Id = 24670 and d_Id = 1247910
会议2 UPDATE table1 SET status = 1 WHERE c_Id = 24671和d_Id = 1247910
答案 0 :(得分:0)
一种避免实际死锁的简单方法是在运行实际更新之前使用SELECT ... FOR UPDATE
:
SELECT * FROM table1 WHERE c_Id = 24670 AND d_Id = 1247910 FOR UPDATE;
然后:
UPDATE table1 SET status = 1 WHERE c_Id = 24670 AND d_Id = 1247910;
使用FOR UPDATE
至少应确保两个事务不会输入涉及更新目标行的同一关键部分。
这不一定意味着像饥饿之类的事情仍然不会发生,但是应该避免正式的僵局。
答案 1 :(得分:0)
以下内容解释了为什么两个并发更新死锁。
session 1
UPDATE table1 SET status = 1 WHERE c_Id = 24670 and d_Id = 1247910;
session 2
UPDATE table1 SET status = 1 WHERE c_Id = 24671 and d_Id = 1247910;
(c_Id,d_Id)是非唯一复合索引。在这种情况下,在默认的REPEATABLE READ事务隔离级别下,行锁和间隙锁都需要,以避免幻像读取。
在没有任何冲突的情况下,会话1最终会将1行锁附加到索引条目(24670,1247910),再加上与索引(24670,1247910)之前/之内/之后不存在的条目上的间隙锁一样多的锁。因为多行在列上可以具有相同的值(c_Id,d_Id);同样,会话2需要在索引条目(24671,1247910)上附加1行锁,并且还要有许多间隙锁。
在以下情况下(按时间顺序)可能会发生死锁:
您可以看到,两个会话都持有一个锁,同时等待对方释放第二个锁,即死锁。如果您足够幸运,则第3步将发生在第2步之前,没有死锁。
当并发会话数较少时,死锁不是一个大问题,因为当系统变量 innodb_deadlock_detect 为ON时,InnoDB会在内部检测到这种情况。如果发生死锁,引擎将简单地以较低的权重回滚事务以打破依赖关系循环。
如果以上死锁情况经常在您的系统中发生,建议您避免使用非唯一索引查询数据。例如,在现有表中添加 UNIQUE 索引,并在可能的情况下通过它查询数据库。当使用的索引唯一时,不再需要间隙锁定,在上述情况下会话不会死锁。