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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
云数据库 RDS PostgreSQL,高可用系列 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/

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
目录
相关文章
|
3月前
|
SQL 关系型数据库 MySQL
Mysql数据恢复—Mysql数据库delete删除后数据恢复案例
本地服务器,操作系统为windows server。服务器上部署mysql单实例,innodb引擎,独立表空间。未进行数据库备份,未开启binlog。 人为误操作使用Delete命令删除数据时未添加where子句,导致全表数据被删除。删除后未对该表进行任何操作。需要恢复误删除的数据。 在本案例中的mysql数据库未进行备份,也未开启binlog日志,无法直接还原数据库。
|
5月前
|
关系型数据库 MySQL Java
字节面试: MySQL 百万级 导入发生的 “死锁” 难题如何解决?“2序4拆”,彻底攻克
字节面试: MySQL 百万级 导入发生的 “死锁” 难题如何解决?“2序4拆”,彻底攻克
字节面试: MySQL 百万级 导入发生的 “死锁” 难题如何解决?“2序4拆”,彻底攻克
|
SQL 关系型数据库 MySQL
MySQL死锁及源码分析!
MySQL死锁及源码分析!
188 0
MySQL死锁及源码分析!
|
SQL 关系型数据库 MySQL
案例剖析:MySQL唯一索引并发插入导致死锁!
案例剖析:MySQL唯一索引并发插入导致死锁!
864 0
案例剖析:MySQL唯一索引并发插入导致死锁!
|
SQL 关系型数据库 MySQL
案例剖析,MySQL共享锁引发的死锁问题!
案例剖析,MySQL共享锁引发的死锁问题!
171 0
|
SQL 关系型数据库 MySQL
遇到mysql数据库死锁,你会怎么排查?
遇到mysql数据库死锁,你会怎么排查?
1068 0
|
12月前
|
存储 关系型数据库 MySQL
从新手到高手:彻底掌握MySQL表死锁
通过本文的介绍,希望你能深入理解MySQL表死锁的概念、原因、检测方法及解决方案,并在实际开发中灵活应用这些知识,提升系统的稳定性和性能。
914 9
|
SQL 算法 关系型数据库
面试:什么是死锁,如何避免或解决死锁;MySQL中的死锁现象,MySQL死锁如何解决
面试:什么是死锁,死锁产生的四个必要条件,如何避免或解决死锁;数据库锁,锁分类,控制事务;MySQL中的死锁现象,MySQL死锁如何解决
|
关系型数据库 MySQL 数据库
一个 MySQL 数据库死锁的案例和解决方案
本文介绍了一个 MySQL 数据库死锁的案例和解决方案。
774 3
|
监控 关系型数据库 MySQL
MySQL锁机制与解决死锁问题
MySQL锁机制与解决死锁问题
628 5

推荐镜像

更多