深入浅出,详解MySql中的各种锁
了解MySQL中的锁机制不仅能帮助我们通过面试,还能提升我们开发数据库应用的能力。锁是保障数据安全和一致性的关键工具,根据不同的操作和场景, MySQL 提供了多种锁类型和策略。以下是对MySQL锁机制的简要概述和实际应用案例。
为什么需要锁?
正如我们在学校图书馆自习时,需要保证自己正在阅读的书籍不被他人随意修改一样,数据库中的锁确保了在并发环境下数据的正确性和一致性。假设多个用户同时试图修改同一条用户记录,没有锁机制的话,数据可能会变得混乱,出现不一致的情况。因此,锁机制应运而生,以防止这种情况的发生。
锁的类型
读锁(共享锁)和写锁(排他锁)
如果我们将共享锁比作多人同时阅读同一本书,那么排他锁就如同有人正在修改这本书,期间其他人不能进行任何修改,只能等待作者完成。例如,当事务A正在更新一条用户记录时,事务B试图更新同样的记录会被阻塞,直到事务A提交或回滚。
间隙锁、临键锁和插入意向锁
间隙锁用于防止“幻读”,即在一个事务中两次读取数据时,期间其他事务插入了新的数据。间隙锁会在数据间隙上加锁,防止这种情况发生。不过,这也可能导致死锁的潜在风险。比如,事务A在查询并加锁某个不存在的数据间隙后,事务B和C试图插入数据到同一间隙,就会被阻塞,形成一个等待链,导致死锁。
悲观锁和乐观锁
悲观锁假设数据会被频繁修改,因此会在读取或写入时总是先加锁。这就像一个谨慎的司机,总是提前刹车以防突发情况。乐观锁则认为数据不常被修改,只在提交时检查是否有冲突。这就像一个自信的骑行者,认为路况良好,不需要频繁减速。
案例分析
假设我们有一个用户表,事务A试图更新某个用户的信息,而事务B试图插入一个新用户。如果事务A加了排他锁,事务B就必须等待,这说明了排他锁的作用。另一方面,如果事务C试图在事务A完成前读取该记录,它会共享锁,允许同时读取而不阻塞,这体现了共享锁的优势。间隙锁的应用则体现在,事务A查询了一个不存在的记录后,其他事务B和C尝试插入到附近的间隙就会被阻塞,避免了幻读的问题。
整体而言,掌握锁机制不仅是技术上的提升,更是对数据库并发控制的深刻理解,帮助我们编写出更加高效和可靠的数据库应用。锁的选择和管理将直接影响应用的性能和稳定性,合理运用这些机制,可以显著提升数据库操作的效率。
深入浅出,详解MySql中的各种锁
-
- 为什么需要锁?
- 1 对数据操作的类型划分
-
- 1.1 读锁(共享/S锁)
- 1.2 写锁(排他/X锁)
- 案例
- 2 锁粒度划分
-
- 2.1 表锁
- 2.2 行锁
-
- 2.2.1 记录锁
-
- 案例
- 2.2.2 间隙锁
-
- 案例
- 2.2.3 临键锁
- 2.2.4 插入意向锁
- 3 锁态度划分
-
- 3.1 悲观锁
- 3.2 乐观锁
- 4 全局锁和死锁
-
- 4.1 全局锁
- 4.2 死锁
其实对于一个开发人员来说,对数据库的掌握并不需要这么深入。
但本人亲身经历,面试的时候来一句“请你说说什么是间隙锁”,一瞬间将我排除在候选人名单外。
而我面试的仅仅是中级开发工程师,可见现在的内卷程度有多严重。
因此本文章整理了一番Mysql中各种锁的作用和原理,欢迎学习交流。
🍅你的关注和点赞对我很重要🍅
为什么需要锁?
我们知道Mysql数据库提供了4种隔离级别来预防我们操作数据的时候可能带来的并发事务问题。
上面的图片相信每个人都烂熟于心了,Mysql默认的隔离级别就是RR(REPEATABLE READ),避免了脏读和不可重复读,我们一般也将这个隔离级别成为:可重复读。
当面试进行到这里,你自我感觉还很良好的时候,面试官突然问你:你知道数据库的隔离级别的原理是什么吗?
好,这个时候,数据库锁的概念就出来了。
下面会通过这个脑图的结构依次简绍:
1 对数据操作的类型划分
1.1 读锁(共享/S锁)
读锁也称共享锁
,多个事务同时读取同一份数据,相互不会影响不会阻塞,数据安全。
给select语句加共享锁
:
Select * from test LOCK IN SHARE MODE;或Select * from test FOR SHARE;
1.2 写锁(排他/X锁)
写锁也称排他锁
,排他排他,顾名思义就是排挤他人。多个事务同时操作(有读有写或同时写)同一份数据,在一个事务还没完成(提交)前,它会阻断其他的事务,保证数据的安全性。
给select语句加排他锁
:
Select * from test FOR UPDATE;
update语句默认会加上排他锁
看到这里你会不会有点感觉了。脏读是不是就是通过排他锁
解决的呢?
案例
有一张user表,其中有这样一行初始数据:
现在模拟两个事务对这行数据进行操作:
- 事务1:
查询user表,加排他锁
- 事务2:
查询user表,加排他锁
2 锁粒度划分
2.1 表锁
表锁这里我们不展开解释了。顾名思义就是每次加锁就把整张表给锁了,一杆子打死。
我们知道Mysql有两种引擎,InnoDB和MyIsam。我们常用的是InnoDB,因为MyIsam不支持事务,并且MyIsam只能表锁,不能行锁。 而InnoDB支持事务,并且可以同时支持表锁
和行锁
。
2.2 行锁
为了提高数据库的并发吞吐量,每次锁定的范围越小越好,但这样的话加锁的数量就会变多,在锁的管理上也是很消耗资源的一件事。
所以,什么时候用表锁
,什么时候用行锁
,需要我们结合实际情况来考虑。
这里我们就先详细了解下我们平时用的最多的行锁
。
2.2.1 记录锁
记录锁
,记录锁
,顾名思义就是给某一条记录加锁。
对于记录锁
而言,它也分为共享锁
和排他锁
,功能参考1.1和1.2的描述。
案例
有一张user表,其中有这样一行初始数据:
现在模拟两个事务对这行数据进行操作:
- 事务1:
修改user表,把张三的年龄改为20,默认加记录锁
(排他锁
,行锁
)
- 事务2:
查询user表,加读锁
2.2.2 间隙锁
间隙锁
是面试种最常问到的东西,听名字感觉很高大上的,其实也是很简单的一个东西。
间隙锁
是防止幻读而产生的一种锁,是加在某一条不存在的数据上的一个锁。
幻读就是事务A第一次读到8条数据,这时候事务B插入了一条,事务A这时第二次读,发现读出来9条数据。多出来的这一条我们成为幻影数据。
案例
初始数据:
间隙锁
的案例我就不截图来演示,用文字描述更易懂。
- 事务A查询id =8的数据,发现查不到数据,并加了
读锁
,未提交。 - 事务B现在要插入一个id为6或7或8或9的数据,不能插入。
- 事务A提交了事务。
- 事务B现在才插入成功。
很明显,解决了幻读的问题。那到底是什么原理呢?为什么事务B不能插入呢?
事务A查询了一个不存在数据,例如id=8的数据。那么就会产生间隙锁
,间隙锁
的这个间隙就会是5——10,也就是当前查寻Id的前后两个数据,id为5和id为10之间不能插入任何数据。
如果事务A查询的id=15,那么根据间隙锁
的原理,插入的数据id如果大于当前表最大的id,则不能插入。这里就是如果事务B要插入id大于10的数据就插入失败。
间隙锁
的危害:间隙锁
是有可能会造成死锁的问题。
2.2.3 临键锁
临键锁
就是记录锁
和间隙锁
的结合,把间隙两头的数据也加上记录锁
。
结合上面的例子,就是:
事务A查询一个id=8的数据,那么5——10区间就会加上间隙锁
,不能插入数据。而5和10这两条数据也会加上记录锁
,不能读写操作。
具体操作就是在事务A进行范围查询的时候,加上for update。
2.2.4 插入意向锁
回顾之前我们的间隙锁,被间隙锁锁定的数据是阻塞、等待。
如果现在有多个插入事务都阻塞了,那他们之间会生成一个内部锁:插入意向锁
如:
- 事务A查询id =8的数据,发现查不到数据,并加了
读锁
,未提交。(5-10之间产生间隙锁
) - 事务B插入一个id为6的数据。阻塞。
- 事务C插入一个id为7的数据。阻塞。
- 事务B,C之间就会产生一个
插入意向锁
。如果B、C插入的数据不冲突,那待事务A释