Mysql 死锁案例6-并发 insert on duplicate key 导致的死锁

本文探讨了在mysql5.7.12中,两个事务并发操作导致的RR隔离级别下死锁现象,涉及事务结构、死锁原因及解决方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

场景复现

mysql 5.7.12 ,事务隔离级别RR

CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE  KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `t` */

insert  into `t`(`id`,`a`,`b`) values 

(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);
事务1事务2
T1

BEGIN;

INSERT  INTO `t`(`id`,`a`,`b`) VALUES (2,2,2)  ON DUPLICATE KEY UPDATE b =666

T2BEGIN
INSERT  INTO `t`(`id`,`a`,`b`) VALUES (3,2,3) ON DUPLICATE KEY UPDATE b =666(阻塞)
T3INSERT  INTO `t`(`id`,`a`,`b`) VALUES (3,3,3)  ON DUPLICATE KEY UPDATE b =666(阻塞)
T4Deadlock found

SHOW ENGINE INNODB STATUS 死锁日志

------------------------
LATEST DETECTED DEADLOCK
------------------------
2024-03-14 00:56:54 0x1b5c
*** (1) TRANSACTION:
TRANSACTION 488617, ACTIVE 4 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 24, OS thread handle 5388, query id 4322 localhost ::1 root update
INSERT  INTO `t`(`id`,`a`,`b`) VALUES (3,2,3) ON DUPLICATE KEY UPDATE b =666
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1087 page no 4 n bits 80 index a of table `test`.`t` trx id 488617 lock_mode X waiting
Record lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 4; hex 80000002; asc     ;;

*** (2) TRANSACTION:
TRANSACTION 488616, ACTIVE 11 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 30, OS thread handle 7004, query id 4327 localhost ::1 root update
INSERT  INTO `t`(`id`,`a`,`b`) VALUES (4,1,1)  ON DUPLICATE KEY UPDATE b =666
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 1087 page no 4 n bits 80 index a of table `test`.`t` trx id 488616 lock_mode X locks rec but not gap
Record lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 4; hex 80000002; asc     ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1087 page no 4 n bits 80 index a of table `test`.`t` trx id 488616 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 4; hex 80000002; asc     ;;

*** WE ROLL BACK TRANSACTION (1)
------------
TRANSACTIONS

死锁分析

  1. T1事务1插入成功,加(0,2)和(2,5)间隙锁写锁、a=2的记录锁写锁和id=2的主键记录锁
  2. T2事务2插入数据发现冲突,申请(0,2]临键锁写锁( insert on duplicate key 语句发现冲突时是加冲突值的临键锁写锁),与事务1的记录锁冲突,阻塞
  3. T1事务1申请(0,2)意向插入锁,与事务2的临键锁冲突得等待,进入死锁

### INSERT ON DUPLICATE KEY UPDATE 中插入意向锁和 Gap 锁导致死锁的原因 在 MySQL 的 InnoDB 存储引擎中,`INSERT ... ON DUPLICATE KEY UPDATE` 是一种常见的操作模式。然而,在高并发环境下,这种语句可能会引发死锁问题。以下是关于其死锁原因以及解决方案的具体分析。 #### 死锁的根本原因 InnoDB 使用两阶段锁定协议来管理事务中的锁请求。当 `INSERT ... ON DUPLICATE KEY UPDATE` 被执行时,如果目标记录不存在,则会尝试向唯一索引的间隙(Gap)加锁以防止其他事务在同一位置插入相同的数据[^1]。具体来说: - **Gap Lock**:用于阻止其他事务在指定范围内的间隙插入新记录。 - **Insert Intention Lock**:这是一种特殊的 Gap Lock,表示某个事务打算在一个特定的位置插入一条记录。它与其他 Insert Intention Lock 或者更强的 Gap Lock 不兼容[^3]。 当两个或更多事务试图通过 `INSERT ... ON DUPLICATE KEY UPDATE` 向同一间隙插入数据时,它们可能分别持有不同的锁并相互等待对方释放资源,从而形成死锁情况[^2]。 #### 解决方案 针对上述提到的由插入意向锁和 Gap 锁引起的死锁现象,可以采取以下几种措施来进行优化和规避: 1. **调整业务逻辑减少竞争** 尽量设计应用程序使得不同事务不会频繁地争抢相同的行或者区间。可以通过预分配 ID 值等方式降低热点区域的压力。 2. **更改隔离级别** 默认情况下,InnoDB 运行于可重复读 (Repeatable Read, RR) 隔离级别之下。虽然这提供了较高的一致性保障但也增加了发生死锁的可能性。考虑将某些不敏感的操作降级至读已提交(Read Committed, RC),这样能有效减少不必要的锁开销。 3. **显式控制事务顺序** 如果能够预测哪些事务之间可能存在潜在冲突,则可以在编码层面强制规定这些事务按照固定次序访问共享资源,进而避免循环依赖关系的发生。 4. **重试机制** 对于不可避免会出现少量随机性死锁的应用场景而言,实现自动化的失败处理流程不失为一个简单有效的办法——即捕获异常后适当延迟再重新发起受影响的操作直至成功为止。 5. **升级数据库版本** 新版 MySQL 和 MariaDB 已经对此类问题做了不少改进工作;因此建议尽可能保持软件处于最新稳定状态以便利用官方提供的修复补丁和技术支持。 ```python try: cursor.execute(""" INSERT INTO table_name (column_list) VALUES (%s,%s,...) ON DUPLICATE KEY UPDATE column=value; """, value_tuple) except OperationalError as e: if 'Deadlock' in str(e): time.sleep(random.uniform(0.1, 0.5)) # Randomized backoff strategy retry_logic() # Recursive call or other recovery method else: raise # Re-throw unexpected errors ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值