INSERT ... ON DUPLICATE KEY UPDATE产生death lock死锁原理

探讨 MySQL InnoDB 引擎下 INSERT...ON DUPLICATE KEY UPDATE 的潜在问题,包括死锁及主从复制数据不一致的风险,并提出相应解决方案。

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

前言

编辑

    我们在实际业务场景中,经常会有一个这样的需求,插入某条记录,如果已经存在了则更新它如果更新日期或者某些列上的累加操作等,我们肯定会想到使用INSERT ... ON DUPLICATE KEY UPDATE语句,一条语句就搞定了查询是否存在和插入或者更新这几个步骤,但是使用这条语句在msyql的innodb5.0以上版本有很多的陷阱,即有可能导致death lock死锁也有可能导致主从模式下的replication产生数据不一致。

正文

    正如前言说的那样,在实际业务中,曾经有过一个需求就是插入一条业务数据,如果不存在则新增,存在则累加更新某一个字段的值,于是乎就想到了使用insert... on duplicate key update这个语句,但是有一天去测试环境查看错误日志时,却发现了在多个事务并发执行同一条insert...on duplicate key update 语句时,也就是insert的内容相同时,发生 了死锁。

  对于insert...on duplicate key update这个语句会引发dealth lock问题,官方文档也没有相关描述,只是进行如下描述:

An INSERT ... ON DUPLICATE KEY UPDATE statement against a table having more than one unique or primary key is also marked as unsafe. (Bug #11765650, Bug #58637)

也就是如果一个表定义有多个唯一键或者主键时,是不安全的,这又引发了以一个问题,见https://bugs.mysql.com/bug.php?id=58637

也就是

     当mysql执行INSERT ON DUPLICATE KEY的 INSERT时,存储引擎会检查插入的行是否会产生重复键错误。如果是的话,它会将现有的
行返回给mysql,mysql会更新它并将其发送回存储引擎。当表具有多个唯一或主键时,此语句对存储引擎检查密钥的顺序非常敏感。根据这个顺序,
存储引擎可以确定不同的行数据给到mysql,因此mysql可以更新不同的行。存储引擎检查key的顺序不是确定性的。例如,InnoDB按照索引添加到
表的顺序检查键。

     insert ... on duplicate key 在执行时,innodb引擎会先判断插入的行是否产生重复key错误,如果存在,在对该现有的行加上S(共享锁)锁,如果返回该行数据给mysql,然后mysql执行完duplicate后的update操作,然后对该记录加上X(排他锁),最后进行update写入。

    如果有两个事务并发的执行同样的语句,那么就会产生death lock,如:


具体的bug描述见:https://bugs.mysql.com/bug.php?id=52020

https://bugs.mysql.com/bug.php?id=58637

编辑

https://bugs.mysql.com/bug.php?id=21356

 

解决办法:

1、尽量不对存在多个唯一键的table使用该语句

2、在有可能有并发事务执行的insert 的内容一样情况下不使用该语句

在 TiDB 中,`INSERT ... ON DUPLICATE KEY UPDATE` 和 `REPLACE` 是两种常见的用于处理数据插入和更新的语句。尽管它们的功能类似,但在实现机制、行为和性能方面存在显著差异。 ### 实现机制 - **INSERT ... ON DUPLICATE KEY UPDATE**:当插入的数据导致唯一键(包括主键)冲突时,会执行指定的更新操作。这种机制允许对现有记录进行部分更新,而不会删除和重新插入整行。 - **REPLACE**:当插入的数据导致唯一键冲突时,TiDB 会先删除冲突的旧记录,然后尝试重新插入新记录。这意味着如果存在多个唯一索引,可能会触发多次删除和插入操作。 ### 行为 - **INSERT ... ON DUPLICATE KEY UPDATE**:在检测到唯一键冲突时,会对相关行以确保事务的隔离性和一致性。由于只是更新现有行,定的范围通常较小。 - **REPLACE**:由于需要先删除冲突的记录再插入新记录,因此涉及更多的操作。删除操作会导致对相关行的独占,插入操作同样需要获取相应的。这可能导致更高的竞争和更长的等待时间。 ### 性能差异 1. **写入吞吐量**: - `INSERT ... ON DUPLICATE KEY UPDATE` 通常具有更高的写入吞吐量,因为它避免了删除和重新插入整行的操作。更新操作仅修改受影响的列,减少了 I/O 操作和日志写入量。 - `REPLACE` 的性能相对较低,因为每次操作都可能涉及两次写入(删除和插入),增了数据库的负担 [^1]。 2. **事务开销**: - `INSERT ... ON DUPLICATE KEY UPDATE` 在事务中只需要一次提交即可完成插入或更新操作,减少了事务的开销。 - `REPLACE` 需要两次独立的操作(删除和插入),每个操作都需要单独的日志记录和事务管理,增了事务的复杂性和开销 [^2]。 3. **索引维护**: - `INSERT ... ON DUPLICATE KEY UPDATE` 只需更新受影响的索引条目,减少了索引重建的成本。 - `REPLACE` 需要先删除旧记录,导致所有相关的索引条目被标记为删除,然后再插入新记录时重新生成索引条目,增了索引维护的开销 [^3]。 4. **并发控制**: - `INSERT ... ON DUPLICATE KEY UPDATE` 在高并发环境下表现更好,因为它对行的定范围较小,减少了竞争的可能性。 - `REPLACE` 由于涉及更多的操作,容易引发死锁或阻塞其他事务,尤其是在大量并发写入的情况下 [^4]。 ### 使用建议 - 如果只需要对现有记录的部分字段进行更新,推荐使用 `INSERT ... ON DUPLICATE KEY UPDATE`,因为它在性能和资源消耗上更为高效。 - 如果确实需要完全替换某条记录(例如,删除旧记录并插入新记录),可以考虑使用 `REPLACE`,但需要注意其带来的额外开销和潜在的竞争问题。 ```sql -- 示例:INSERT ... ON DUPLICATE KEY UPDATE INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.com') ON DUPLICATE KEY UPDATE name = 'Alice', email = 'alice@example.com'; -- 示例:REPLACE REPLACE INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.com'); ``` ###
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值