关于死锁:故障分析-MySQL死锁案例分析

作者:杨奇龙 网名“北在北方”,资深 DBA,次要负责数据库架构设计和运维平台开发工作,善于数据库性能调优、故障诊断。 本文起源:原创投稿 *爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 一 背景死锁,其实是一个很有意思也很有挑战的技术问题,大略每个DBA和局部开发同学都会在工作过程中遇见 。 本次分享的一个死锁案例是 波及通过辅助索引的更新以及通过主键删除导致的死锁。心愿可能对想理解死锁的敌人有所帮忙。 二 案例剖析2.1 业务逻辑select for update 表记录并加上 x 锁,查问数据,做业务逻辑解决,而后删除该记录。还有其余业务逻辑要更新记录,导致死锁。 2.2 环境阐明数据库 MySQL 8.0.30 事务隔离级别 REPEATABLE-READ create table dl(id int auto_increment primary key,c1 int not null ,c2 int not null,key idx_c1(c1));insert into dl(c1,c2) values (3,1),(3,2),(3,2),(3,3),(4,4),(5,5);2.3 测试用例 2.4 死锁日志------------------------LATEST DETECTED DEADLOCK------------------------2022-12-03 16:43:59 140261132850944*** (1) TRANSACTION:TRANSACTION 1416764, ACTIVE 15 sec starting index readmysql tables in use 1, locked 1LOCK WAIT 5 lock struct(s), heap size 1128, 3 row lock(s)MySQL thread id 15, OS thread handle 140261086668544, query id 283 localhost msandbox updatingupdate dl set c2=10 where c1=5*** (1) HOLDS THE LOCK(S):RECORD LOCKS space id 49 page no 5 n bits 80 index idx_c1 of table `test`.`dl` trx id 1416764 lock_mode XRecord lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0*** (1) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 49 page no 4 n bits 80 index PRIMARY of table `test`.`dl` trx id 1416764 lock_mode X locks rec but not gap waitingRecord lock, heap no 7 PHYSICAL RECORD: n_fields 5; compact format; info bits 32*** (2) TRANSACTION:TRANSACTION 1416759, ACTIVE 23 sec updating or deletingmysql tables in use 1, locked 1LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1MySQL thread id 16, OS thread handle 140261085611776, query id 286 localhost msandbox updatingdelete from dl where id=6*** (2) HOLDS THE LOCK(S):RECORD LOCKS space id 49 page no 4 n bits 80 index PRIMARY of table `test`.`dl` trx id 1416759 lock_mode X locks rec but not gapRecord lock, heap no 7 PHYSICAL RECORD: n_fields 5; compact format; info bits 32*** (2) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 49 page no 5 n bits 80 index idx_c1 of table `test`.`dl` trx id 1416759 lock_mode X locks rec but not gap waitingRecord lock, heap no 8 PHYSICAL RECORD: n_fields 2; compact format; info bits 0*** WE ROLL BACK TRANSACTION (2)2.5 死锁剖析sess1 开启一个事务,在T2 时刻执行 select for update,持有id=6的lock_mode X record lock.sess2 在T3 时刻执行依据c1=5的更新,然而其加锁程序是先在索引idx_c1上加锁,顺利加锁,而后到申请加主键上加id=6的锁,发现sess1曾经持有主键 id=6 的X的锁,因而须要期待。如日志中 (1) TRANSACTION: 中 WAITING FOR的提醒 RECORD LOCKS space id 49 page no 4 n bits 80 index PRIMARY of table test.dl trx id 1416764 lock_mode X locks rec but not gap waitingsess1 执行 delete id=6 的操作,因为事务自身曾经持有了主键上的锁,删除记录同时要对索引idx_c1上的记录加上 lock_mode X record lock,发现该锁曾经被sess2持有,造成了死锁条件,sess1 报错,产生回滚。2.6 如何解决本文中死锁的起因是因为 sess2 通过辅助索引进行更新,因而举荐的防止死锁计划是把sess2 应用辅助索引的更新改成基于主键进行更新,从而防止申请idx_c1上的加锁造成循环期待产生死锁。 ...

January 5, 2023 · 2 min · jiezi

关于死锁:新特性解读-MySQL-80死锁日志改进

作者:胡呈清 爱可生 DBA 团队成员,善于故障剖析、性能优化,集体博客:https://www.jianshu.com/u/a95...,欢送探讨。 本文起源:原创投稿 *爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 重要改良MySQL8.0 的死锁日志能够看到事务1持有的锁信息了: 这对咱们剖析死锁无疑是个很好的帮忙,而在 MySQL5.7 是没有这个信息的,始终饱受诟病: 注意事项然而这在某些状况下可能会产生一些误会,比方事务1持有锁和期待锁是同一个锁: 为什么会呈现这种状况?这是不是bug?必须不是bug,咱们来复现这一死锁场景: ##设置RC隔离级别CREATE TABLE `t2` ( `c1` int(11) NOT NULL, `c2` int(11) DEFAULT NULL, PRIMARY KEY (`c1`), UNIQUE KEY `c2` (`c2`));insert into t2 values(1,1),(5,4),(20,20); 死锁逻辑: session2 插入胜利,对 c2 索引 10 这一记录加 X Lock,即死锁日志中的lock_mode X locks rec but not gap;session1 插入时,产生惟一键抵触,须要对 c2 索引 10 这一记录加 S Lock,带 gap 属性,即锁的范畴为 (4,10]。然而因为 session2 曾经对记录加了 X Lock,与 S Lock 互斥,所以必须期待 session 2 先开释锁,也就是死锁日志中的lock mode S waiting;session2 再次插入 9,在 (4,10] 范畴内,这个地位有 session1 的 gap 锁(尽管还在锁队列中,没有加上),插入意向锁会被 gap 锁阻塞,即死锁日志中的 lock_mode X locks gap before rec insert intention waiting。session1、session2 相互期待,所以造成死锁。session1 期待获取的锁 S Lock 阻塞了 session2 将要获取的锁,这在 MySQL8.0 中就会显示成 session1 持有的锁,同时也是 session1 期待的锁。就是这样。 ...

December 23, 2021 · 1 min · jiezi

关于死锁:LiteOSSpinLock自旋锁及LockDep死锁检测

摘要:除了多核的自旋锁机制,本文会介绍下LiteOS 5.0引入的LockDep死锁检测个性。2020年12月公布的LiteOS 5.0推出了全新的内核,反对SMP多核调度性能。想学习SMP多核调度性能,须要理解下SpinLock自旋锁。除了多核的自旋锁机制,本文还会介绍下LiteOS 5.0引入的LockDep死锁检测个性。 本文中所波及的LiteOS源码,均能够在LiteOS开源站点https://gitee.com/LiteOS/LiteOS 获取。 自旋锁SpinLock源代码、开发文档,LockDep死锁检测个性代码文档列表如下: kernelincludelos_spinlock.h 自旋锁头文件网页获取自旋锁源码 https://gitee.com/LiteOS/Lite...。 spinlock.S、arch/spinlock.h 自旋锁汇编代码文件及头文件 针对不同的CPU架构,有两套代码。因为自旋锁实用于多核,M核架构archarmcortex_m下不蕴含自旋锁的汇编文件。如下: archarmcortex_a_r架构 汇编代码文件https://gitee.com/LiteOS/Lite...。头文件https://gitee.com/LiteOS/Lite...。archarm64架构 汇编代码文件 https://gitee.com/LiteOS/Lite...。头文件https://gitee.com/LiteOS/Lite...。开发指南自旋锁文档在线文档https://gitee.com/LiteOS/Lite...。 LockDep死锁检测 死锁检测代码蕴含: 头文件https://gitee.com/LiteOS/Lite...C代码文件https://gitee.com/LiteOS/Lite...。咱们首先来看看自旋锁。 1、SpinLock 自旋锁在多核环境中,因为应用雷同的内存空间,存在对同一资源进行拜访的状况,所以须要互斥拜访机制来保障同一时刻只有一个核进行操作。自旋锁就是这样的一种机制。 自旋锁是指当一个线程在获取锁时,如果锁曾经被其它线程获取,那么该线程将循环期待,并一直判断是否可能胜利获取锁,直到获取到锁才会退出循环。因而倡议爱护耗时较短的操作,避免对系统整体性能有显著的影响。 自旋锁与互斥锁比拟相似,它们都是为了解决对共享资源的互斥应用问题。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个持有者。然而两者在调度机制上略有不同,对于互斥锁,如果锁曾经被占用,锁申请者会被阻塞;然而自旋锁不会引起调用者阻塞,会始终循环检测自旋锁是否曾经被开释。自旋锁用于多核不同CPU核查资源的互斥拜访,互斥锁用于同一CPU核内不同工作对资源的互斥拜访。 自旋锁SpinLock外围的代码都在kernelincludelos_spinlock.h头文件中,蕴含struct Spinlock构造体定义、一些inline内联函数LOS_SpinXXX,还有一些LockDep死锁检测相干的宏定义LOCKDEP_XXXX。 1.1 Spinlock 自旋锁构造体自旋锁构造体Spinlock定义如下,次要的成员变量为size_t rawLock,这是自旋锁是否占用持有的胜利的标记:为0时,锁没有被持有,为1时示意被胜利持有。当开启LockDep死锁检测调测个性时,会使能另外3个成员变量,记录持有自旋锁的CPU核信息、工作信息。 struct Spinlock { size_t rawLock; /**< 原始自旋锁 */#ifdef LOSCFG_KERNEL_SMP_LOCKDEP UINT32 cpuid; /**< 死锁检测个性开启时,持有自旋锁的CPU核 */ VOID *owner; /**< 死锁检测个性开启时,持有自旋锁的工作的TCB指针 */ const CHAR *name; /**< 死锁检测个性开启时,持有自旋锁的工作的名称 */#endif};1.2 Spinlock 自旋锁罕用函数接口LiteOS自旋锁模块为用户提供上面几种性能,蕴含自旋锁初始化,申请/开释,查问自旋锁状态等。自旋锁相干的函数、宏定义只反对SMP - Symmetric MultiProcessor模式,当单核UP - UniProcessor时,函数不失效。接口详细信息能够查看API参考。 1.2.1 自旋锁初始化自旋锁初始化的内联函数如下,其中参数SPIN_LOCK_S *lock,即自旋锁构造体指针,其中SPIN_LOCK_S是Spinlock的typedef别名,在kernelincludelos_lockdep.h文件中定义的。 自旋锁初始时,会把自旋锁标记为0:lock->rawLock = 0,当开启死锁检测个性时,也会做相应的初始化。 LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_SpinInit(SPIN_LOCK_S *lock){ lock->rawLock = 0;#ifdef LOSCFG_KERNEL_SMP_LOCKDEP lock->cpuid = (UINT32)-1; lock->owner = SPINLOCK_OWNER_INIT; lock->name = "spinlock";#endif}LOS_SpinInit()是动静初始化的自旋锁,LiteOS还提供了动态初始化自旋锁的办法SPIN_LOCK_INIT(lock): ...

February 27, 2021 · 4 min · jiezi

关于死锁:故障分析-全局读锁一直没有释放发生了什么

作者:刘开洋爱可生交付服务部团队北京 DBA,次要负责解决 MySQL 的 troubleshooting 和我司自研数据库自动化治理平台 DMP 的日常运维问题,对数据库及周边技术有浓重的学习趣味,喜爱看书,谋求技术。本文起源:原创投稿* 爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。问题:在一个客户的线上监控告警中,提醒主从提早一直升高,咱们登上数据库进行查看一下,发现 MySQL 从库复制状态提醒 SQL 线程在 waiting for global read lock。 在数据库的过程列表中发现了存在的期待全局读锁和 kill slave 的过程;高可用在一直重启复制,起因是因为 NAT 网络中域名反解析出错导致高可用软件对复制的误判。 这里就能看出两个问题,第一是有个下发全局读锁的对象,个别在从库上就是备份工具了,第二就是 slave 正在被 kill,而且工夫相当长,因而这里可能存在一种非凡的死锁。 查看 mysql 过程时的偶合下,发现 mysqldump 过程已存在 10 多个小时,比照等 FTWRL 的过程的工夫,就坐实了下发全局读锁的对象是 mysqldump: 线上没有开启 performance_schema 的 instruments 和 consumers(PS:这个对于锁监控很重要,肯定记得关上)。如果开启了 performance_schema,能够通过 metadata_locks 查到相干锁记录,这个咱们在前面的复现中看一下。 上述情况剖析得出存在一个非凡的死锁,造成 MySQL Server 层和存储引擎层的死锁闭环,而且不能齐全追踪到所有锁记录。 解决:这样三个锁组合成的死锁在其余客户端执行 UNLOCKS TABLE 是解不开的,只须要 kill 掉全局读锁或者期待全局锁的锁一个即可,因为没有找到全局锁对应的线程,这里将等全局锁的线程 kill 掉,数据库就复原了,再看残留的 mysqldump 过程隐没了: 两个锁就此解开了 故障复原,提早追平。 ...

January 11, 2021 · 2 min · jiezi