开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第十一天,点击查看活动详情
大家好,我是Zhan,一名个人练习时长一年半的大二后台练习生,最近在学MySQL高级篇,现在正在看面经整理遗漏知识点,欢迎各路大佬一起交流讨论
👉本篇速览
在前面对MySQL高级篇的的学习中,我们从存储引擎、索引、锁、SQL语句优化、事务实现、常用工具这些方面学习了MySQL的底层知识,如果有需要了解的可以去看我前面的文章。
谈到锁,死锁一直是一个值得讨论的话题,也是我们需要避免的情况,在了解到锁的使用后,我们就需要考虑什么时候会出现死锁、为什么出现死锁、又如何避免死锁,本文将从死锁的出现、死锁的底层原理、死锁的解决方法这几个方面来详细解读MySQL的死锁,当然看本文之前如果不了解三种锁以及查询过程中的锁的构建情况,建议先看这两篇文章:「MySQL高级篇」MySQL全局锁、表级锁、行级锁、「MySQL高级篇」你真的知道查询的时候有哪些锁吗?,那么我们就正式开始今天的讲解,今天的讲解将围绕下面四个问题展开,循序渐进:
- 1️⃣ 死锁是什么呢?(What)
- 2️⃣ 什么时候会出现死锁呢?(When)
- 3️⃣ 为什么会出现死锁呢?(Why)
- 4️⃣ 如何解决死锁的问题呢?(How)
1️⃣ 死锁概述
在正式开始今天的讲解之前,我们先回顾一下死锁的相关知识
死锁是指两个或者两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而导致的一种阻塞的现象,如果没有外力,他们将一直等待下去。
就跟卡bug一样,比如说你去面试,面试官问你:MySQL为什么会死锁;你告诉面试官:你录用我我就告诉你,面试官说:你告诉我我就录用你,然后你两就一直这么你问我我问你,这就是死锁。
那么,什么时候会发生死锁呢?
这就不得不提死锁的四个必要条件:互斥、占有并等待、非抢占、循环等待
- 互斥:也就是说至少有一个资源处于独占的状态,也就是说不能被两个线程同时使用
- 占有并等待:一个进程至少占有一个资源,并且等待另一个资源,但是这个资源被别人占有了
- 非抢占式:咱就是说线程不能去抢占别的线程占有的资源,只能自己主动释放
- 循环等待:A等着B的资源,B等着C的资源,C又等着A的资源
2️⃣ 死锁的发生
现在我们模拟一个死锁的场景,在此之前我们先创建一张团队表用于存储一个排球队的人员信息:
CREATE TABLE `team` (
`id` int NOT NULL AUTO_INCREMENT,
`position_no` int DEFAULT NULL,
`user_name` VARCHAR(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_position` (`position_no`) USING BTREE
) ENGINE=InnoDB ;
并插入五行数据:
现如今球队招募一个二传手和一个副攻手,也就是position_no = 6 和 position_no = 7的人,但是在招募之前需要确认这个位置是否有人,球队的两个经理往表中插入数据的时候发生如下的事情,此时就会发生死锁的情况:
经理A(清水) | 经理B(谷地) |
---|---|
查询位置为6的人员是否存在 select * from team where position_no = 6 for update | |
查询位置为7的是否存在人员 select * from team where position_no = 7 for update | |
insert into team(position_no, user_name) values(6,研磨) | |
insert into team(position_no, user_name) values(7,列夫) |
首先解释一下为什么使用的是当前读,因为如果是快照读就会出现的情况为:两个经理都要插入位置为6的人员,并且查询的时候都发现位置存在,就都进行了插入,即:
经理A(清水) | 经理B(谷地) |
---|---|
查询位置为6的人员是否存在 select * from team where position_no = 6 for update | |
查询位置为7的是否存在人员 select * from team where position_no = 6 for update | |
insert into team(position_no, user_name) values(6,研磨) | |
insert into team(position_no, user_name) values(6,及川) |
此时就会发生两个人员都被加入进来了,出现了两个位置为6的记录,出现了幻读,因此在查询的时候需要加锁,也就是使用当前读
回到刚刚的场景,我们去实际环境试一下会得到的结果是:经理A插入位置为6的人员,经理B插入位置为7的人员的时候,这两句插入都阻塞了,也就是发生了死锁,在下面我们会分析为什么出现死锁:
3️⃣ 死锁的底层原理分析
其实有了昨天的知识储备,了解了查询的时候的加锁情况,我们其实不难分析出为什么会死锁:
select * from team where position_no = 6 for update语句属于非唯一索引的等值查询,会加上(6,+∞]的临键锁
select * from team where position_no = 7 for update语句属于非唯一索引的等值查询,会加上(7,+∞]的临键锁
两个事务都持有范围为(6,+∞]的临键锁,而接下来的插入操作会去获取插入意向锁,插入意向锁与临键锁互斥,因此获取插入意向锁需要对方的事务的临键锁释放,于是就出现了循环等待,也就是死锁
4️⃣ 如何避免死锁
在数据库层面,MySQL给我们提供了两种策略来打破死锁:
- 设置事务等待锁的超时时间,也就是说如果事务中一直阻塞,在超过设置的innodb_lock_wait_timeout做个参数的值之后,可以让事务超过指定时间后自动回滚并释放锁
- 开启主动死锁检测:这是MySQL提供的死锁检测,如果这个机制发现了死锁,就会回滚其中的一个事务,让其他的事务得到执行,那么所有的事务就都解开了,设置的方法为:innodb_deadlock_detect = on即可
在业务层面,我们在处理业务逻辑的时候,主动的去寻找死锁存在的可能性,从根源解决问题,并加以修正,比如如果是防止订单号重复,也就是防止查重,我们可以修改订单号的生成规则,以雪花算法或者Redis去生成订单号,或者说可以给订单号这个字段加上唯一的索引……
💬 总结
今天的文章只是带大家简单走了一遍MySQL的死锁情况,关于为什么会死锁,讲完昨天的文章,也就是对查询时的加锁情况的讲解,其实来分析这个死锁的情况并不是一件难事,最后也介绍了从数据库层面和业务层面如何去防止MySQL出现死锁的情况。
📣 下集预告
讲了老半天的锁去解决幻读的问题,那么它真正完全解决了幻读的问题吗,也就是说对于RR事务隔离级别,幻读问题是否被完全解决了呢?我们下篇来详细解读,明天见~
🍁 友链
- 「MySQL高级篇」MySQL存储引擎
- 「MySQL高级篇」MySQL索引入门
- 「MySQL高级篇」MySQL索引进阶
- 「MySQL高级篇」MySQL语句优化
- 「MySQL高级篇」MySQL全局锁、表级锁、行级锁
- 「MySQL高级篇」详解InnoDB存储引擎
- 「MySQL高级篇」详解MVCC
- 「MySQL高级篇」MySQL常用工具
- 「MySQL高级篇」COUNT聚合函数、模糊查询与索引失效
- 「MySQL高级篇」你真的知道查询的时候有哪些锁吗?
✒写在最后
都看到这里啦~,给个点赞再走呗~,也欢迎各位大佬指正,在评论区一起交流,共同进步!也欢迎加微信一起交流:Goldfish7710。咱们明天见~