MySQL insert 遇到delete 唯一键未提交导致死锁

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 一 前言死锁,其实是一个很有意思也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见 。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助二案例分析2.1 业务场景用户录入商品,应用程序会提前检查是否存在相同记录,如果有则先删除再插入;如果没有则直接插入。2....

一 前言

死锁,其实是一个很有意思也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见 。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助

二案例分析

2.1 业务场景

用户录入商品,应用程序会提前检查是否存在相同记录,如果有则先删除再插入;如果没有则直接插入。

2.2 环境说明

MySQL 5.7.22 事务隔离级别为RC模式。

create table t(id int not null auto_increment primary key ,
a int not null default 0,
b int not null default 0,
c int not null default 0,
unique key uk_ab(a,b)) engine=innodb;
insert into  t(a,b,c) values(1,1,1),(3,3,2),(6,6,3),(9,9,5);

2.3 背景知识

知识点一

INSERT操作在插入或更新记录时,检查到 duplicate key或者有一个被标记删除的duplicate key(本文的案例),对于普通的INSERT/UPDATE,会加LOCK_S属性锁next-key lock。而对于类似REPLACE INTO或者INSERT … ON DUPLICATE这样的SQL加的是X锁。而针对不同的索引类型也有所不同:

代码位置 row0ins.cc:2013

if (flags & BTR_NO_LOCKING_FLAG) {            
/* Set no locks when applying log in online table rebuild. */        
} 
else 
if (allow_duplicates) 
{ /* If the SQL-query will update or replace duplicate key we will take X-lock for duplicates ( REPLACE, LOAD DATAFILE REPLACE, INSERT ON DUPLICATE KEY UPDATE). */
err = row_ins_set_exclusive_rec_lock(lock_type, block, rec, index, offsets, thr);
} 
else {
err = row_ins_set_shared_rec_lock(
lock_type, block, rec, index, offsets, thr);
}

知识点二

当向某个数据页中插入一条记录时,总是会调用函数 lock_rec_insert_check_and_lock 进行锁检查(构建索引时的数据插入除外),会去检查当前插入位置的下一条记录上是否存在锁对象,这里的下一条记录不是指的物理连续,而是按照逻辑顺序的下一条记录。 如果下一条记录上不存在锁对象:若记录是二级索引上的,先更新二级索引页上的最大事务ID为当前事务的ID;直接返回成功。

如果下一条记录上存在锁对象,就需要判断该锁对象是否锁住了GAP。如果GAP被锁住了,并判定和插入意向GAP锁冲突,当前操作就需要等待,加的锁类型为LOCKX | LOCKGAP | LOCKINSERTINTENTION,并进入等待状态。 代码位置 lock0lock.cc:5965

*inherit = TRUE;/* If another transaction has an explicit lock request which locksthe gap, 
waiting or granted, on the successor, the insert has to wait.
An exception is the case where the lock by the another transactionis a gap type lock 
which it placed to wait for its turn to insert. 
We do not consider that kind of a lock conflicting with our insert. 
This eliminates an unnecessary deadlock which resulted 
when 2 transactionshad to wait for their insert. 
Both had waiting gap type lock requestson the successor,
which produced an unnecessary deadlock. */
const ulint     
type_mode = LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION;
const lock_t*  wait_for = lock_rec_other_has_conflicting(type_mode, block, heap_no, trx);
if (wait_for != NULL) 
{    
RecLock rec_lock(thr, index, block, heap_no, type_mode);   
trx_mutex_enter(trx);    
err = rec_lock.add_to_waitq(wait_for);    
trx_mutex_exit(trx);
} 
else { 
err = DB_SUCCESS;
}

我通过如下测试进行验证。表结构和数据是 2.2 中的构造测试数据。

测试案例一

sess1 mysql > delete from t where a=3 and b=3 ;
Query OK, 1 row affected (0.00 sec)
sess2 mysql >update t set  c=6 where  a=6 and b=6 and c=3;
sess1 mysql >insert into t(a,b,c) values(3,3,5); --产生锁等待

insert (3,3,5) 申请lock S 被sess2 delete  持有的Lock X 行锁阻塞,

show engine innodb status 并没有完整的显示 该lock S 是什么锁。我们继续测试。

测试案例二

T1 sess1
mysql > delete from t where a=3 and b=3 ;
mysql > insert into t(a,b,c) values(3,3,5); 

T2 sess2 
mysql > insert into t(a,b,c) values(3,2,6);

T3 sess3
mysql > insert into t(a,b,c) values(3,4,5);

其中 sess2 sess3 等待申请lock_mode X locks gap before rec insert intention waiting,显然是被sess1持有的LOCK S Next key lock阻塞. 而且是(1,3),(3,6)两个区间的

显然测试案例一中 sess2 持有记录(6,6)的lock X record lock but not gap,会阻塞 insert (3,3)申请LOCK S Next key lock .

2.4 测试用例

2.5 死锁日志

***(1) TRANSACTION:
TRANSACTION 2489, ACTIVE 43 sec inserting
mysql tables inuse1, locked 1
LOCK WAIT 5lockstruct(s), heap size 1136,4 row lock(s), undo log entries 2
MySQL thread id 121125, OS thread handle 139804595451648, query id 526 localhost msandbox update
insert into t(a,b,c) values(3,3,3),(3,1,2)
***(1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 26 page no4 n bits 80 index uk_ab of table `test`.`t` trx id 2489lock mode S waiting
***(2) TRANSACTION:
TRANSACTION 2490, ACTIVE 36 sec inserting
mysql tables inuse1, locked 1
6 lockstruct(s), heap size 1136,6 row lock(s), undo log entries 3
MySQL thread id 121123, OS thread handle 139804615882496, query id 528 localhost msandbox update
insert into t(a,b,c) values(6,6,6),(6,5,4)
***(2) HOLDS THE LOCK(S):
RECORD LOCKS space id 26 page no4 n bits 80 index uk_ab of table `test`.`t` trx id 2490 lock_mode X locks rec but not gap
***(2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 26 page no4 n bits 80 index uk_ab of table `test`.`t` trx id 2490 lock_mode X locks gap before rec insert intention waiting
*** WE ROLL BACK TRANSACTION (1)

2.6 分析死锁日志

T1 delete a=3 and b=3 未提交,持有二级索引(3,3)行锁,记录被标记为失效。

T2 delete a=6 and b=6 未提交,持有二级索引(6,6)行锁,记录被标记为失效。

T3 insert (3,3,3), 检查到被标记为删除的(3,3),申请加上LOCK_S next-key lock。但是在检查到下一条记录持有Lock X record lock 。于是等待。

T4 insert (6,5,4) 写入(3,6)的区间,申请lock_mode X locks gap before rec insert intention waiting,但是需要等待T3会话LOCK_S next-key lock。于是相互等待,发生死锁。

2.7 解决方法

本质上是并发操作相邻记录导致死锁。和开发沟通,将业务逻辑做修改,如果发现录入的商品记录数和存在的记录数一样就做更新,不存在的则直接写入。降低直接操作相邻记录的可能性。

三 小结

以上分析是基于自己半路出家的阅读代码能力的出来的,不一定完全正确。如果大家有其他意见,请拍砖。

四 参考文章

percona 5.7.22 源代码 以下是我翻阅的相关函数

row_ins_sec_index_entry_lowrow_ins_scan_sec_index_for_duplicate  
-- row_ins_set_shared_rec_lock     
-- lock_clust_rec_read_check_and_lock     
-- lock_sec_rec_read_check_and_lockbtr_cur_ins_lock_and_undo  
--lock_rec_insert_check_and_lock    
--lock_rec_other_has_conflictinglock_sec_rec_read_check_and_lock  
--lock_rec_lock    
--lock_rec_lock_slow      
--lock_rec_other_has_conflicting

http://mysql.taobao.org/monthly/2016/01/01/

http://mysql.taobao.org/monthly/2017/12/02/

http://mysql.taobao.org/monthly/2018/05/04/

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
28天前
|
SQL 关系型数据库 MySQL
遇到mysql数据库死锁,你会怎么排查?
遇到mysql数据库死锁,你会怎么排查?
73 0
|
1月前
|
关系型数据库 MySQL Shell
MySQL回滚脚本: 误操作delete binlog回滚shell脚本
MySQL回滚脚本: 误操作delete binlog回滚shell脚本
|
1月前
|
存储 自然语言处理 关系型数据库
MySQL全文索引源码剖析之Insert语句执行过程
【8月更文挑战第17天】在MySQL中,处理含全文索引的`INSERT`语句涉及多步骤。首先进行语法解析确认语句结构无误;接着语义分析检查数据是否符合表结构及约束。随后存储引擎执行插入操作,若涉及全文索引则进行分词处理,并更新倒排索引结构。此外,事务管理确保了操作的完整性和一致性。通过示例创建含全文索引的表并插入数据,可见MySQL如何高效地处理此类操作,有助于优化数据库性能和提升全文搜索效果。
|
1月前
|
存储 SQL 关系型数据库
深入MySQL锁机制:原理、死锁解决及Java防范技巧
深入MySQL锁机制:原理、死锁解决及Java防范技巧
|
1月前
|
SQL JavaScript 关系型数据库
Mysql索引不当引发死锁问题
本文通过真实案例解析了MySQL在高并发环境下出现死锁的问题。数据库表`t_award`包含多个索引,但在执行特定SQL语句时遭遇索引失效,导致更新操作变慢并引发死锁。分析发现,联合索引`(pool_id, identifier, status, is_redeemed)`因`identifier`允许为空值而导致索引部分失效。此外,`pool_id`上的普通索引产生的间隙锁在高并发下加剧了死锁风险。为解决此问题,文中提出了调整索引顺序至`(pool_id, status, is_redeemed, identifier)`等方案来优化索引使用,进而减轻死锁现象。
|
1月前
|
Oracle 关系型数据库 MySQL
Mysql和Oracle数据库死锁查看以及解决
【8月更文挑战第11天】本文介绍了解决MySQL与Oracle数据库死锁的方法。MySQL可通过`SHOW ENGINE INNODB STATUS`查看死锁详情,并自动回滚一个事务解除死锁;也可手动KILL事务。Oracle则通过查询V$LOCK与V$SESSION视图定位死锁,并用`ALTER SYSTEM KILL SESSION`命令终止相关会话。预防措施包括遵循ACID原则、优化索引及拆分大型事务。
|
1月前
|
关系型数据库 MySQL
解决MySQL insert出现Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘xxx‘ at row 1
解决MySQL insert出现Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘xxx‘ at row 1
53 2
|
1月前
|
存储 关系型数据库 MySQL
在 MySQL 中使用 Insert Into Select
【8月更文挑战第11天】
90 0
在 MySQL 中使用 Insert Into Select
|
25天前
|
监控 关系型数据库 MySQL
MySQL死锁是什么
【8月更文挑战第26天】MySQL死锁是指两个或多个事务在执行过程中,因争夺锁资源而造成的相互等待的现象,若无外力干涉,它们都将无法继续执行。这种相互等待的情况会导致整个系统陷入停滞状态,影响数据库的性能和稳定性。
37 0
|
2月前
|
SQL 存储 关系型数据库
细说 MySQL 死锁
【7月更文挑战第26天】MySQL 死锁
29 4

热门文章

最新文章