11. mysql锁机制_小伙一步步试探MySQL锁机制,居然是这样?

感谢你的三连,关注不迷路,我是Bill,一个5年京漂,有问题可以私信我,我会尽力帮助解决你的问题。

港真,我发现你们都是这样的程序员,只收藏不点赞,唢呐版的安河桥送给自己。

91ef4aea40bab1c087662534f575157d.png

好吧,你们是不是觉得太长不想看,收藏了以后再看,然后就没有然后了。

c6867d29a32a31a8d616fd38a3c4e8e4.png

你们不仁,我也不能不义,今天含泪(捂脸)继续分享手撕MySQL系列-锁机制。

先来看一下面试中可能会导致你被pass的问题:

什么是MySQL的锁,它有几种类型?锁的优化策略有哪些?

5914772c61f1b23a7a6b2da2107b270b.png

锁的定义

锁是计算机协调多个进程或线程并发访问某一资源的机制。锁保证数据并发访问的一致性、有效性;Mysql锁在服务器层和存储引擎层进行并发控制。

MySQL InnoDB 锁的基本类型

这里为什么要单独拎出InnoDB的锁类型加以讲解,大部分情况下我们使用的MySQL引擎是InnoDB,他的锁类型相对于MyISAM也较复杂。MyISAM只有表级别的Read和Write锁。

官网把锁分成了 8 类。

2f82d9c5015ae4f4e728ad6f7a9bbf4c.png

我们把前面的两个行级别的锁(Shared and Exclusive Locks),和两个表级别的锁(Intention Locks)称为锁的基本模式。

后面三个 Record Locks、Gap Locks、Next-Key Locks,我们把它们叫做锁的算法, 也就是分别在什么情况下锁定什么范围。

这么多洋文码子,你可能又说老子都不想看了,都不知道什么意思,安排!

Shared and Exclusive Locks (共享和排他锁)Intention Locks (意向锁)Record Locks (记录锁)Gap Locks (间隙锁)Next-Key Locks (临键锁)是不是有些关键字你听说过了?

在分析他们之前我们再来补充一点基础知识。

锁的粒度

InnoDB里既有行级别的锁,又有表级别的锁;表锁,顾名思义,是锁住一张表;行锁就是锁住表里面的一行数据。看一下这两种锁的一些差异

锁定粒度,表锁肯定是大于行锁的。加锁效率,表锁是大于行锁的。为什么?表锁只需要直接锁住这张表就行了,而行锁,还需要在表面去检索这一行数据,所以表锁的加锁效率更高。冲突概率,表锁大于行锁,因为当我们锁住一张表的时候,其他任何一个事务都不能操作这张表。但是 我们锁住了表里面的一行数据的时候,其他的事务还可以来操作表里面的其他没有被锁 定的行,所以表锁的冲突概率更大。表锁的冲突概率更大,所以并发性能更低,表锁的并发性能是小于行锁的。上面已经说了,InnoDB既支持行锁也支持表锁,这是从锁定粒度(或者说是锁定范围)上划分的,那么从功能方式上我们再来看看锁的别名们。

先来看看行锁的两种方式。

共享锁 (Shared Locks)

第一个行级别的锁就是我们在官网看到的 Shared Locks (共享锁),我们获取了 一行数据的读锁以后,可以用来读取数据,所以它也叫做读锁,多个事务可以共享一把读锁。

灵魂一问:你知道怎么给一行数据加上读锁吗?

我们可以用 select …… lock in share mode; 的方式手工加上一把读锁。

释放锁有两种方式,只要事务结束,锁就会自动事务,包括提交事务和结束事务。

开始手撕,我们用nivacat实际操作看一下,读锁是不是可以重复获取。

9572627af3cc19827ad3cde748092649.png

图1

图1我们对id=1的数据加上了一把读锁,事务并没有关闭,此时再开启一个查询界面,再次对该行数据获取读锁。

9a8a09b84410f7f6c7e628099a4b764f.png

图2

图2可以看到,是没有问题的,可以对一行数据重复加读锁。

排他锁(Exclusive Locks)

第二个行级别的锁叫排它锁,它是用来操作数据的,所以又叫做写锁。只要一个事务获取了一行数据的排它锁,其他的事务就不能再获取这一行数据的共享锁和排它锁。

排它锁的加锁方式有两种,第一种是自动加排他锁。我们在操作数据的时候,包括增删改,都会默认加上一个排它锁。

还有一种是手工加锁,我们用一个 FOR UPDATE 给一行数据加上一个排它锁,这个无论是在我们的代码里面还是操作数据的工具里面,都比较常用。

再来验证以下写锁:

9cadad1c74fe7c720d7f8191ed3d65eb.png

图3

如图3对id=1的数据加上一把写锁,事务并没有结束,此时再次对id加读锁。

717745720eb7a4d72c8e965400583993.png

图4

此时可以看到图4右上角红框位置出现了一朵菊花表示加载中,其实就是线程被阻塞掉了,因为图3的事务还没有结束释放写锁。

结论:对一条数据加写锁之后则不再允许其他事务对该行加读锁。

再来看看加写锁会怎么样:

87efadfe25bcaacc54895293f65c6e2b.png

图5

同样被阻塞,结论:对一条数据加写锁之后则不再允许其他事务对该行加写锁。

我们尝试删除他试一下:

772e5ddd8188147bb4bc20022b626ba0.png

图6

图6的菊花依然在,还是被阻塞。

总结以下:只要一个事务获取了一行数据的排它锁,其他的事务就不能再获取这一行数据的共享锁和排它锁。

行锁看完了,再来看一下表锁。

意向锁(Intention Locks)

意向锁,听起来好像很厉害的样子。它是由MySQL维护的,当我们给一行数据加上共享锁时,数据库会自动在这张表上面加一个意向共享锁。当我们给一行数据加上排他锁之前,数据库会自动在这张表上面加一个意向排他锁。

换一个方式说,你来品一品表级别的意向锁它有什么用。

如果一张表上至少有一个意向共享锁,说明有其他的事务给其中的某些数据行加上了共享锁。

如果一张表上至少有一个意向排他锁,说明有其他的事务给其中的某些数据行加上了排他锁。

你品,你细品。

我来帮你品一品。

第一、有了表级别的锁,在 InnoDB 里面就可以支持更多粒度的锁(这看起来像是废话)

第二、如果说没有意向锁的话,当我们准备给一张表加上表锁的时候,我们首先要去判断有没其他的事务锁定了其中了某些行,这个时候我们就要去扫描整张表才能确定能不能成功加上一个表锁,如果数据量特别大,比如有上千万的数据的时候,加表锁的效率就会很低。

第三、引入了意向锁之后就不一样了。我只要判断这张表上面有没有意向锁,如果有,就直接返回失败。如果没有,就可以加锁成功。所以 InnoDB 里面的表锁,可以把它理解成一个标志。就像火车上厕所有没有人使用的灯,是用来提高加锁的效率的。

好了,锁的四种基本模式,讲完了:

行锁:共享锁,排他锁;

表锁:意向共享锁;意向排他锁;

锁了半天,你可能觉得,哦,我懂了,行锁锁的是行,表锁锁的是表,很简单嘛!老子聪明极了。

真是这样吗?

注意:以下内容可能会引起不适,建议提前看博主的另一篇手撕B+树的文章【当我们聊数据库索引时,我们该聊些什么】

当然如果你对索引很熟悉,继续看吧少年。

先撸一个没有主键的表:

88f04984ed442d0564b6fffe619fc4ee.png

图7

5965344cdbd44373d67102d445938f8a.png

图8

图8是表中的数据,注意id并不是主键。

尝试对id=5的数据加写锁。

bf19d857f1e9a1fbef7919e5d1151028.png

图9

理论上我们加的是行级写锁,只会锁住id=5的一行数据,那来尝试锁一下id=10的数据;

bd0689f8d06566c613685c3e5ae610e5.png

图10

what?? 右上角菊花出现了,被阻塞了??

明明是行锁,为啥锁住了其他行,其实你可以试一下,整张表都被锁住了。

不卖关子了,这张表博主一开始就说了,它没有主键,也就是说你以为的id=5在MySQL看来并不能唯一命中聚集索引的主键,当表没有主键时,MySQL其实是为表创建了隐式的_rowId来作为聚集索引的主键,当你使用id=5为条件为数据加锁时,实际上MySQL只能全表扫描去找到它,所以只能锁表。

加上主键(过程略),我们再来看看。

f0227dc91d8414358be71461653ec394.png

图11

先对id=1加写锁,再来对id=2加写锁

3e5711ab2c17cd65675324d9587845d0.png

图12

没问题,菊花没出现,是行锁本锁没错了。

扩展一下,如果对图12中的表,加上一个name的辅助索引,我们使用

begin;select * from user where name = 'Bill' for update;

加上一把锁,此时锁住的是什么?欢迎留言交流。

以下是MySQL加锁的算法,有兴趣可以继续手撕。

Record Locks(记录锁)

当我们对于唯一性的索引(包括唯一索引和主键索引)使用等值查询,精准匹配到一条记录的时候,这个时候使用的就是记录锁。比如 where id = 1,如图11、12,已经演示过了。使用不同的id加锁,不会冲突,只会锁住这个记录。

间隙锁(Gap Locks)

当我们查询的记录不存在,没用命中任何一条记录时,无论是用等值查询还是范围查询的时候,它使用的都是间隙锁。重复一遍,当查询的记录不存在的时候,使用间隙锁。间隙锁主要是阻塞插入 insert。相同的间隙锁之间不冲突。

上图:先上一把锁

d536a5a2bded0624da291aaf5a2cc11c.png

图13

尝试insert数据

be521afc1886a85387e7e5fe0c1b79d4.png

图13

图13 这时候想去插入id=7的数据,菊花出现了,被阻塞了,你可能会说,你锁的就是id=7的数据啊,好,我们再来试一下

a223165bb559c0e0345266b607a0f7ec.png

图14

如图,表中数据并没有id为2和3的数据

dd568a2a2c94a3310051fec7eaf78826.png

图15

图15,这时候我们插入id=3的数据,被阻塞了。

c7740b2af71b2e4ce902aa494d985421.png

图16

如图16所示,间隙锁会把(没有结束的)事务查询没有结果的区间(左开右开)锁住。图14的查询条件也可以换成id=3,效果一样。

间隙锁只在 RR中存在。如果要关闭间隙锁,就是把事务隔离级别设置成 RC, 并且把 innodb_locks_unsafe_for_binlog 设置为 ON。

Next-Key Locks (临键锁)

当我们使用了范围查询,不仅仅命中了 Record 记录,还包含了 Gap 间隙,在这种情况下我们使用的就是临键锁,它是 MySQL 里面默认的行锁算法,相当于记录锁加上间隙锁。

它的退化情况:唯一性索引,等值查询匹配到一条记录的时候,退化成记录锁。 没有匹配到任何记录的时候,退化成间隙锁。

举个例子

024c6fd062430fc1a51f01018c8762e1.png

图17

对于图17表,使用查询

b5ec32c371588b3a5ea5a8f26e991371.png

图18

命中了一条数据,此时再次尝试一下锁住的区间,就不一一带着尝试了,试过之后你会发现;

它会锁住(1, 5] 和(5,7]区间, 也就是说,临键锁会锁住命中的最后记录的下一个左开右闭区间。

还记得什么是幻读吗?读到了其他事务已提交的新增记录的情况叫幻读。试想一下,当你查询时,临键锁已经把你的范围锁定了,其他事务自然无法提交你查询范围内的新增记录,幻读也就迎刃而解了。

锁的优化建议

使用锁,就自然会遇到死锁的情况,如何避免死锁在操作系统级别也给出了答案,一定避免环路等待,就是你等待我,同时我又等待你的情况,合理顺序访问;

避免没有where条件的操作;

大事务分解成小事务;使用等值查询而不是范围查询等等。

....请你再补充一些。

看,这就是编程干货晒场,就在这晒,晒足三百六十五天。关注我嘛。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值