更新语句可以使用插入意图锁吗?

时间:2019-04-19 05:36:11

标签: mysql

数据库遇到并发更新两个事务导致的死锁问题。

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

2 个答案:

答案 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行锁,并且还要有许多间隙锁。

在以下情况下(按时间顺序)可能会发生死锁:

  1. 会话1锁定(24670,1247910)
  2. 会话2锁定(24671,1247910)
  3. 会话1尝试获取覆盖范围(24670,1247910)<(c_Id,d_Id)<(24671,1247910)的间隙锁;为此,会话1需要先扫描(24670,1247910)之后的所有条目,然后找出大于(24670,1247910)的最小元素。因此,它需要在当前由会话2持有的相邻条目(24671,1247910)上附加锁。 会话2尝试获取覆盖相同范围的间隙锁;按照相同的逻辑,为了知道小于(24671,1247910)的最大元素,会话2需要将行锁附加到当前由会话1持有的索引条目(24670,1247910)。

您可以看到,两个会话都持有一个锁,同时等待对方释放第二个锁,即死锁。如果您足够幸运,则第3步将发生在第2步之前,没有死锁。

当并发会话数较少时,死锁不是一个大问题,因为当系统变量 innodb_deadlock_detect 为ON时,InnoDB会在内部检测到这种情况。如果发生死锁,引擎将简单地以较低的权重回滚事务以打破依赖关系循环。

如果以上死锁情况经常在您的系统中发生,建议您避免使用非唯一索引查询数据。例如,在现有表中添加 UNIQUE 索引,并在可能的情况下通过它查询数据库。当使用的索引唯一时,不再需要间隙锁定,在上述情况下会话不会死锁。