这个不可思议的死锁你会解吗?
| 作者王起帆,腾讯CSIG数据库产品中心后台开发工程师,目前主要参与DBbrain开发工作,热爱技术,欢迎留言进行交流。
我们都知道,数据库系统中,不同线程并发访问数据,为了保护数据,在执行SQL语句时候需要对数据加锁。而死锁是一个经常遇到问题,SQL语句加锁和事物隔离级别,访问的索引是不是唯一,访问数据是否存在都有关系,往往死锁分析非常复杂。这篇文章将介绍一个"简单的死锁",这个死锁产生的事物中SQL语句都只有一条,而且业务非常简单就是删除一条记录。两个事物同时执行以下两个SQL语句就有可能死锁。DELETE FROM dept_manager WHERE num = 0;DELETE FROM dept_manager WHERE dept_no = "d001";
一、死锁模拟
死锁模拟
首先介绍下表结构,这个表除了主键索引 PRIMARY,还有一个唯一索引 num 和一个非唯一索引 dept_no ,建表语句如下:CREATE TABLE `dept_manager` ( `emp_no` int(11) NOT , `dept_no` char NOT , `num` int(11) NOT , `to_date` date NOT , PRIMARY KEY (`emp_no`), unique index(`num`), KEY `dept_no` (`dept_no`)) ENGINE=InnoDB DEFAULT CHARSET=latin1;
然后再准备下数据:INSERT INTO `dept_manager` VALUES (1001,"d001",0,"1991-10-01"), (1002,"d005",,"9999-01-01"), (1005,"d002",,"1989-12-17"), (1007,"d002",,"9999-01-01"), (1008,"d004",,"1988-09-09"), (1009,"d004",,"1992-08-02"), (1010,"d005",9,"1996-08-30");
使用执行两个sql很难,使用 mysqlslap 来高并发碰碰运气:# mysqlslap --create-schema dldb -q "begin;DELETE FROM dept_manager WHERE num = 0; rollback;" --number-of-queries=100000 -uroot -p123456 mysqlslap --create-schema dldb -q "begin;DELETE FROM dept_manager WHERE dept_no = "d001"; rollback;" --number-of-queries=100000 -uroot -p123456 &
这两个事物非常都是删除一行相同的数据 (1001,"d001",0,"1991-10-01")只不过一个根据索引 num ,一个根据索引 dept_no 。
二、原因分析
1. 数据是怎么找到的?
要说清楚死锁产生原因,就要先理清楚这条SQL是怎么执行的,会在那些地方加锁。在此之前先说说数据库是怎么找到我们要删除的这行数据的。下面两幅图展示根据年龄为30来查记录的示意图。首先根据 name 为 seven, 在 name 这个辅助索引查找,但是只能拿到主键的 id。随后再根据主键id 去主键查找,这个过程称为回表。访问数据是要通过索引的,而且数据就在主键索引上面,所以加锁就是加在索引上面的。
2. Delete 是怎么执行的
Delete 删除数据其实并不是把数据删除了,只是把数据标记一下,表示这里可以复用的,如果下次这里有数据要插入就可以直接复用原来空间里。所以Delete 和 Update 操作比较类似。Delete 和 Update 是根据条件找到第一条数据,进行修改,然后找到第二条数据,以此类推直到再也查不到符合条件的数据。
3. 加锁分析
我们以 DELETE FROM dept_manager WHERE num = 0; 为例,只有一个条件 num = 0, 因该是根据 num = 0 在 num 索引中找到对应的主键id, 随后根据主键 id,找到对应记录,标记成可复用状态。除了删除数据行记录,对应的索引也需要维护下,其他索引对应位置也需要标记成删除状态。这个表中主键索引 PRIMARY,唯一索引 num,非唯一索引 dept_no 的对应位置都会加上锁。同理第二个SQL语句执行时候,加锁位置也是一样的。(可重复度隔离级别上,非唯一索引还要加上间隙锁)。
既然加锁上一样的,那应该是在不同索引加锁顺序是不一样的。推测下对于 WHERE num = 0 应该先在 num 上加锁,随后在主键加锁,最后在 dept_no上,num ->PRIMARY-> dept_no。WHERE dept_no = "d001";加锁顺序应该是dept_no -> PRIMARY -> num。尽管这条SQL数据很简单,但是由于数据中索引比较多,加锁顺序也不一样,导致了死锁。
三、场景验证
可以用 show engine innodb status ,来查看最近一次死锁日志。事物1等待索引dept上的锁 0: len 4; hex 64303031; asc d001;; 这里"64303031" 16进制转为字符为"d001" 与 WHERE dept_no = "d001" 相对应。事物2持有这个锁的,事物1持有的锁没有显示,应该是主键上的锁,这是符合预期的。------------------------LATEST DETECTED DEADLOCK------------------------2021-04-27 16:41:19 0x70000a6b1000*** TRANSACTION:TRANSACTION 1681994, ACTIVE 0 sec updating or deletingmysql tables in use , locked LOCK WAIT lock struct(s), heap size 1136, row lock(s), undo log entries MySQL thread id 30, OS thread handle 123145456488448, query id 343687 localhost 127.0.0. root updatingDELETE FROM dept_manager WHERE num = 0*** WAITING FOR THIS LOCK TO BE GRANTED: #请求 dept_no上锁RECORD LOCKS space id 367 page no 5 n bits 80 index dept_no of table `employees`.`dept_manager` trx id 1681994 lock_mode X locks rec but not gap waitingRecord lock, heap no PHYSICAL RECORD: n_fields ; compact format; info bits 0 0: len ; hex 64303031; asc d001;; : len ; hex 800003e9; asc ;;
*** TRANSACTION:TRANSACTION 1681554, ACTIVE 0 sec starting index readmysql tables in use , locked lock struct(s), heap size 1136, row lock(s)MySQL thread id 106, OS thread handle 123145477099520, query id 341105 localhost 127.0.0. root updatingDELETE FROM dept_manager WHERE dept_no = "d001"*** HOLDS THE LOCK(S): # 持有 dept_no 上锁RECORD LOCKS space id 367 page no 5 n bits 80 index dept_no of table `employees`.`dept_manager` trx id 1681554 lock_mode XRecord lock, heap no PHYSICAL RECORD: n_fields ; compact format; info bits 0 0: len ; hex 64303031; asc d001;; : len ; hex 800003e9; asc ;;
*** WAITING FOR THIS LOCK TO BE GRANTED: # 请求主键RECORD LOCKS space id 367 page no n bits 80 index PRIMARY of table `employees`.`dept_manager` trx id 1681554 lock_mode X locks rec but not gap waitingRecord lock, heap no PHYSICAL RECORD: n_fields 6; compact format; info bits 32 0: len ; hex 800003e9; asc ;; : len 6; hex 00000019aa4a; asc J;; : len ; hex c000001b80ede; asc , ;; : len ; hex 64303031; asc d001;; : len ; hex 80000000; asc ;; 5: len ; hex f8f41; asc A;;
*** WE ROLL BACK TRANSACTION
四、总结
本文介绍的样例中,尽管SQL语句很简单,但由于表中有多个索引,对索引的访问顺序不同,造成死锁风险。为了避免数据库中发生死锁,建议:
1. 尽量开启死锁检测;
2. 尽量使用小事务,在业务允许范围内,将隔离级别改成读已提交,可以减少不些不必要的锁;
3. 避免全表扫描;
4. 避免较多索引;
5. 不同事务对表和行操作的顺序尽量一致。
- End -
封针疗法广受质疑,宝宝肌张力异常要紧吗?近日,某大V公众号发布文章,对郑州某医院儿童康复科,发明的位点加穴位药物注射疗法(俗称封针疗法)提出质疑,在医学界一时引起巨大争议。原文章,现在已经被删除,我们不知道为什么删除。文
一文秒懂,家有2岁内宝宝你必须要知道的事导读平时大家总会留言问好多问题,今天正好,借这个机会,一次性回答大家一些有代表性的常见问题!机会难得!欢迎分享给其他有困扰的妈妈群!孩子不爱吃饭,需要补锌吗?判断宝宝是否缺锌,单看
胎毒不可怕,就怕没文化今天叨叨G和大家聊一个非常具有中国特色的话题,很多准妈妈在孕晚期都在忙活的一个工作去胎毒为了去胎毒,民间还流传着各种各样的去胎毒偏方。相信很多妈妈都听过,有的还试过吧!什么给孕妇准
宝宝放屁响又臭还蹦屎,不可忽视的健康信号不知道小仙女们有没有这种感受,自从当了妈,感觉自己三观尽毁。直接从水瓶拧不开的弱女子,变成水桶都提得动的女汉子,瞬间转换没有过渡的那种。更别说以前的谈屎色变了,现在就算是吃饭的时候
宝宝吃手,越吃越聪明吗?全天下的宝宝都是一样的可爱,让人忍不住亲亲抱抱举高高不知道你们发现没,他们还有一个共同的最大的爱好吃手手!有些家长听到这个就闹心,宝宝吃手会不会将细菌吃到肚子里,影响健康?会不会影
这种胎记竟然会癌变?如何分辨?很多宝宝都是自带纹身出生的,左青龙,右白虎,屁股上有个狮子在跳舞也有家长发现自家宝宝身上洁白如雪,什么都没有啊。那只能恭喜你咯宝宝出生屁股上大块的淤青,身上甚至脸上各种红色粉色咖啡
爸妈带娃到底差在哪?奶爸的自我修养常言道父爱如山。熟悉叨叨画风的小仙女们,一定都知道下一句,对没错,就是你想的那句!父爱如山,一动不动。为什么会有这样的说法?还不都是因为在现实生活中,爸爸们带孩子的劣迹层出不穷,不
断奶宝宝哭了3天3夜,决定之前先看这篇眼看着天气一天比一天的转凉,又迎来了坊间一波断奶大潮,但随之而来的还有断奶相关问题。本文炒鸡全面,必须分享哦妈妈们可以看自己感兴趣的小标题选择需要的内容浏览写文不易,多多鼓励!无论
孩子吐字不清说话晚是发育迟缓?哪样算语言发育正常?每个孩子都是上天的恩赐,每位家长也都会有自己孩子最可爱最厉害的感觉。但当看到同龄孩子小嘴巴拉巴拉说个没完,再看看自家孩子咿咿呀呀连个整句都说不明白时,难免会有些失落。那么,孩子语言
06岁孩子全类型绘本推荐(怎么选都吐血整理好了!)每个孩子都是小天使,他们迷人的笑可爱的闹,每一分每一秒都让人想拥抱可爱是可爱,但有时又让人很无奈,尤其是问一些家长无法回答的问题时候我不喜欢刷牙,为什么要刷牙?我不想和妈妈分开我为
孩子烧抽了怎么办?会烧坏脑子吗?热性惊厥再发作的几率如果孩子发生第一次热性惊厥是1岁以内,那将来再次发生热性惊厥的几率是50如果是1岁以上发生第一次热性惊厥,那么再次发生热性惊厥的可能性将降低到30。发生热性惊厥