mysql 死锁优化_MySQL优化--INSERT ON DUPLICATE UPDATE死锁

本文介绍了MySQL中INSERT ON DUPLICATE UPDATE操作可能导致死锁的原因及加锁流程。在高并发环境下,由于间隙锁(GAP锁)的存在,两个会话尝试更新相同键值的记录时,可能会形成死锁。为了避免死锁,建议在生产环境中减少使用此类语句,转而采用普通的INSERT操作,配合异常处理来实现'存在则更新,不存在则插入'的功能。

INSERT ON DUPLICATE UPDATE与死锁

在MySQL中提供两种插入更新的方式:REPLACE INTO和INSERT ON DUPLICATE UPDATE,简化了“存在则更新,不存在则插入”的实现逻辑,但这两种方式在MySQL内部都被拆分为多个操作步骤且引入GAP锁来保证数据完整性,因此在高并发情况下极易产生死锁。

##==================================================##

在MySQL中INSERT ON DUPLICATE UPDATE的加锁流程:

1.Innodb存储引擎尝试INSER INTO操作

2.如果插入成功,则忽略DUPLICATE UPDATE部分并返回

3.如果插入失败,则表明有相同记录存在,对该记录加共享锁(S)

4.执行DUPLICATE UPDATE语句,对记录加X锁,然后更新记录。

##==================================================##

对于INSERT ON DUPLICATE UPDATE操作,当两个会话S1和S2使用INSERT ON DUPLICATE UPDATE语句操作相同数据且表中存在相同键值记录时,触发死锁场景为:

1.由于表中已存在重复键值的记录,导致会话先后尝试INSER失败

2.会话S1进入步骤3尝试获取记录的S锁,该记录未被其他会话加锁,获取S锁成功。

3.会话S2进入步骤3尝试获取记录的S锁,该记录上被加持S锁,但由于S锁与S锁兼容,获取S锁成功

4.会话S1进入步骤4尝试获取记录的X锁,由于会话S2对该记录持有S锁,S锁与X锁不兼容,获取X锁失败,会话S1被阻塞

5.会话S2进入步骤4尝试获取记录的X锁,由于会话S1对该记录持有S锁,S锁与X锁不兼容,获取X锁失败,会话S2被阻塞

6.会话S2被阻塞后进入死锁检查环节,发现阻塞S1->S2和S2->S1形成死锁环路,触发死锁机制强制回滚S1或S2事务。

##==================================================##

在MySQL默认隔离级别REPEATABLE READ下,为避免出现"幻读"发生,防止其他会话插入相同键值的记录。

对于普通INSERT操作加锁如下:

1.对于非唯一索引,需要对新记录加排他锁(X),另外对新记录和新记录的相邻记录的区间加gap锁。

2.对于唯一索引,仅需要对新记录加排他锁(X),唯一索引特性保证其他会话无法插入相同键值。

对于INSERT ON DUPLICATE UPDATE操作,当表中未存在重复键值记录时,加锁特点如下:

1.对于唯一索引和非唯一索引,都需要对新记录加排他锁(X),另外对新记录和新记录的相邻记录的区间加gap锁。

##==================================================##

准备测试数据:

CREATE TABLE `tb2002` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`c1` varchar(20) DEFAULT NULL,

`c2` varchar(20) DEFAULT NULL,

`c3` int(11) DEFAULT '0',

PRIMARY KEY (`id`),

UNIQUE KEY `idx_c1` (`c1`,`c2`)

) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;

insert into tb2002(c1,c2) values('a','1');

insert into tb2002(c1,c2) values('f','1');

开始测试:

会话1执行:

insert into tb2002(c1,c2) values('d','2') ON DUPLICATE KEY UPDATE c3=c3+1;

执行成功

会话2执行:

insert into tb2002(c1,c2) values('c','1') ON DUPLICATE KEY UPDATE c3=c3+1;

执行被阻塞,等待X+GAP锁,GAP锁的行记录目标是('f','1')

会话1执行:

insert into tb2002(c1,c2) values('d','1') ON DUPLICATE KEY UPDATE c3=c3+1;

触发死锁,会话2被回滚,会话1执行成功

##==================================================##

通过上面两个死锁案例,我们强烈建议在生产环境中尽量避免使用REPLACE INTO和INSERT INTO ON DUPLICATE UPDATE语句,改用普通INSERT操作,并对INSER操作部分代码加入异常加查,当INSERT失败时改为UPDATE操作。

### 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、付费专栏及课程。

余额充值