Mysql-InnoDB存储引擎中-锁介绍

最近数据库的学习都是基于InnoDB存储引擎的,这一篇去学习第6章锁的部分。

之前有一篇是关于数据库ACID是基于什么保证的,ACD都分析过了,今天关于I-隔离性数据库中是基于锁来保证的。

1. lock 和latch

latch主要保证并发线程操作临界资源的正确性,没有死锁检测的机制。

lock主要针对事务,锁定包括表,页,行,在commit或者rollback后释放。存在死锁机制。


2. 锁的类型

2.1 InnoDB存在两个行级别的锁:

S Lock(共享锁),允许事务读一行数据。

X Lock(排他锁),允许事务删除或者更新一行数据。

注意,InnoDB同样也支持表级别的S和X锁,不要误认为S和X锁就只是针对行的。

共享锁默认是并存的,但是共享锁和排他锁之间不能共存。


2.2 InnoDB也存在额外的锁方式-意向锁:

意向锁的级别是表级别,分为两种:

IS Lock(意向共享锁),事务想要获得一张表中某几行的共享锁。

IX Lock(意向排他锁),事务想要获得一张表中某几行的排他锁。


关于此图有几点需要特别注意的,不然会影响你的认知:

IS/IX/S/X在此处都是针对的表级别在讨论,不要认为此处的S和X是说的行级别,受这个影响的人会对加锁的过程感到疑惑的,可能大家都是受了翻译书的影响,因为,书中在介绍这一个图是是这么说的,原文如下,注意文中说的故表级意向锁和行级锁的兼容性如下,这是错误的说法。


为什么我说这么说是错误的?看下面一个简单的例子:

1.SessionA 请求table IX(IX是mysql自动请求的,IS也是一样)--成功,SessionB 请求table IX -成功。

2.SessionA 请求某一行的 X,发现已经有其他Session有IX了,根据图的说法是冲突了。

3.同理SessionB也是一样。

那么问题来了:两个不同的事务去更新同一个表中的不同的数据时还会受到锁的影响吗?

根据图上的说法,会的呀,你看嘛,不是说冲突了吗????????

一切的原因都是因为我上面标注的那句话是错误,导致了读者对于这个概念的错误理解,而且我发现Mysql-InnoDB内幕详解中错误的说法还存在于很多地方,希望大家读的时候要头脑清醒。

我们在2.1里面也说了S和X锁同样也是存在表级别的。来看下面一句话的截图:


总结一下:

IX,IS是表级锁,不会和行级别的X,S冲突。只会和表级别的X,S冲突。比如说alter操作,drop操作,lock操作是不是对于书里那句话很讽刺啊,说实话我看的时候也是一脸懵逼,这什么时候出现了这个概念了?比较的竟然是表级别和行级别的。

那么有了这个理解,对于上面的例子的第2部是不是就说的通了?可以正常获取到X锁,不冲突。

理解到此,意向锁的作用到底是干嘛的呢?同样用例子来理解:

1.SessionA 锁住了table中某一行,存在了S行锁。

2.SessionB 申请整个table的X锁。假设申请成功了,注意这边只是假设成功。

3.SessionB在理论上能进行某一行的修改了,但是此时SessionA是存在了S锁的,冲突了。

因此数据库为了去解决这种冲突的造成,需要让Session的申请被阻塞,直到SessionA释放出来。需要怎么判断?

步骤1:判断表是否被其他事务用表锁锁住。

步骤2:判断表中某一行是否被锁住。(但是如果需要判断某一行的话,太费力了,需要扫描很多数据

为了解决这个问题,引出了意向锁,如果有意向锁的话,那么步骤2不需要去扫描表中很多数据,只要判断当前是否存在IS锁就行(因为例子里SessionA获得是行锁S,因此数据库会自动帮助我们获取到表级IS锁)。

3. 一致性非锁定读(普通读)

我们知道,在一般使用时,如果当前事务正在修改某一个数据,那么另一个事务还是可以正常读取到这个数据,不会因此而等待,此时读取到的数据其实是一份快照数据。这种技术成为多版本控制(MVCC)。

那么它是怎么读取到的呢?之前有了undo和redo的学习,我们知道MVCC是通过undo实现的,此时读的数据其实是读取的undo log内的快照数据,也变相的说明数据其实不是最新的。

mysql在读已提交和可重复读的隔离级别下使用的都是非锁定的一致性读,其实这个概念之前在学习postgres时已经有了解,只是说法没有这么专业而已,传送门1

读已提交:读取的是select语句执行时的数据,因此会存在不可重复读和幻读的问题。

可重复读:读取的是事务开始时的数据,因此不可重复读解决了,但是幻读不能避免(InnoDB也有优化,可以解决幻读,稍后讲解)。

4. 一致性锁定读(当前读)

叫的很专业,其实大家也都见过............通俗一点就是显式的加锁,控制住读取出来的数据。

4.1 select ... for update(加X行锁)

4.2 select ... lock in share mode(加S行锁)

小测试模块:

lock in share mode 

1. 两个不同的Session执行lock in share mode时。SessionA和SessionB的读取没有问题,但是当SessionA再想去更新这一条记录时发现当前数据被SessionB锁住了,因此它必须等到SessionB释放掉锁之后才能正常更新。所以对于这种存在并发场景时还是需要用 for update。


2. 只有一个Session时,可以正常更新。


for update

当前数据为下面:


其实我们如果想要唯一并锁定数据而且还要更新的话,最好是采用select ..for update的方式。这种方式其实也是解决多线程更新问题的一种方式,还有一种就是在程序内通过Java层面的锁去解决,一个道理。


5. 锁的实现算法

5.1 Record Lock(以下简称RL):单行锁定

5.2 Gap Lock(以下简称GL):范围锁定,不包括当前行

5.3 Next-Key Lock(以下简称NKL):Record+Gap,锁定一个范围,包括范围本身。

RL锁住的是索引记录,因此如果建立的时候没有指定索引,会选择一个隐式主键。

NKL锁住的是范围加本身,例如,存在索引10,11,13,20。那么会被分成(-无穷,10],(10,11],(11,13],(13,20],(20,+无穷)。

其实从书中的观点可以得出一些结论,就是InnoDB的锁都是基于索引存在的。如果没有索引的话,不存在所谓的加行锁。

此篇文章不去深入追究sql语句加何种锁的问题,只探讨一些专业讲解,后篇会去逐一学习。传送门

在RR(可重复读)的隔离机制下,mysql是解决了幻读的问题,那么它是怎么控制住的呢?就是采用的NKL的加锁方式,因为NKL中的GL算法控制住了在并发事务下可能会将数据加在同一范围内,从而导致RR级别下读出多的条数,这也就是为什么幻读是针对条数。

那么可以显示的关闭GL:

1.隔离机制改成RC(读已提交)。

2.参数innodb_locks_unsafe_for_binlog设置为1。

猜你喜欢

转载自blog.csdn.net/qq_32924343/article/details/80451875