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/

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
3月前
|
SQL 关系型数据库 MySQL
MySQL死锁及源码分析!
MySQL死锁及源码分析!
MySQL死锁及源码分析!
|
3月前
|
SQL 关系型数据库 MySQL
案例剖析:MySQL唯一索引并发插入导致死锁!
案例剖析:MySQL唯一索引并发插入导致死锁!
228 0
案例剖析:MySQL唯一索引并发插入导致死锁!
|
3月前
|
SQL 关系型数据库 MySQL
案例剖析,MySQL共享锁引发的死锁问题!
案例剖析,MySQL共享锁引发的死锁问题!
|
23天前
|
存储 关系型数据库 MySQL
从新手到高手:彻底掌握MySQL表死锁
通过本文的介绍,希望你能深入理解MySQL表死锁的概念、原因、检测方法及解决方案,并在实际开发中灵活应用这些知识,提升系统的稳定性和性能。
158 9
|
5月前
|
SQL 关系型数据库 MySQL
遇到mysql数据库死锁,你会怎么排查?
遇到mysql数据库死锁,你会怎么排查?
371 0
|
2月前
|
SQL 算法 关系型数据库
面试:什么是死锁,如何避免或解决死锁;MySQL中的死锁现象,MySQL死锁如何解决
面试:什么是死锁,死锁产生的四个必要条件,如何避免或解决死锁;数据库锁,锁分类,控制事务;MySQL中的死锁现象,MySQL死锁如何解决
|
3月前
|
关系型数据库 MySQL 数据库
一个 MySQL 数据库死锁的案例和解决方案
本文介绍了一个 MySQL 数据库死锁的案例和解决方案。
197 3
|
4月前
|
监控 关系型数据库 MySQL
MySQL锁机制与解决死锁问题
MySQL锁机制与解决死锁问题
364 5
|
3月前
|
监控 关系型数据库 MySQL
一次彻底讲清如何处理mysql 的死锁问题
【10月更文挑战第16天】本文详细介绍了如何处理 MySQL 中的死锁问题,涵盖死锁的概念、原因、检测方法及解决策略,强调通过优化事务设计、调整数据库参数、手动处理和预防措施等手段,有效减少死锁,提升数据库性能与稳定性。
556 0
|
5月前
|
存储 自然语言处理 关系型数据库
MySQL全文索引源码剖析之Insert语句执行过程
【8月更文挑战第17天】在MySQL中,处理含全文索引的`INSERT`语句涉及多步骤。首先进行语法解析确认语句结构无误;接着语义分析检查数据是否符合表结构及约束。随后存储引擎执行插入操作,若涉及全文索引则进行分词处理,并更新倒排索引结构。此外,事务管理确保了操作的完整性和一致性。通过示例创建含全文索引的表并插入数据,可见MySQL如何高效地处理此类操作,有助于优化数据库性能和提升全文搜索效果。