MySQL(九):锁、表锁、行级锁、Gap Lock、Next-Key Lock

文章详细阐述了数据库中锁的类型和作用,包括并发事务访问的三种情况,写-写、读-写情况下的问题及解决方案。介绍了InnoDB存储引擎的表锁和行级锁,如共享锁、独占锁、多粒度锁以及MVCC在一致性读中的应用。同时,讨论了InnoDB中的意向锁和行级锁的各种类型,如RecordLock、GapLock和Next-KeyLock,以及如何防止幻读现象。
摘要由CSDN通过智能技术生成

一、锁

1.1 并发事务访问记录的三种方式

并发事务访问相同记录的情况大致划分为3种:

  • 读-读情况:并发事务同时读取相同的记录,不会引起问题
  • 写-写情况:并发事务相继对相同记录进行改动,可能出现一致性问题
  • 读-写情况:一个事务进行读取操作,另一个事务进行改动操作,可能出现一致性问题

1.2 写-写情况

在写-写情况下,有可能发生脏写现象,脏写在任何一种隔离级别下都是不允许发生的,所有多个未提交的事务想要相继操作同一记录时,需要加锁排队执行,
"锁"本质上是内存中的一种结构,在事务执行之前是没有锁的,当一个事务想要对这条记录进行改动时,首先会看看当前内存中有没有与这条记录相关的锁结构,如果没有,会生成一个,在这里插入图片描述

  • trx:表示当前锁与哪个事务相关联
  • is_waiting:表示当前事务是否在等待

如果在T1提交之前,事务T2想要修改这条记录,发现内存中有关于该记录的一条锁,T2也会生成一个锁结构,并将is_waiting属性设置为true,表示加锁失败

在这里插入图片描述
事务T1提交之后,会把T1生成的锁结构释放掉,然后检测还有没有与该记录关联的锁结构,结果发现T2在等待获取锁,因此将T2生成的锁结构的is_waiting设置为false,并将该事务对应的线程唤醒

1.3 读-写情况

在读-写的情况下会出现脏读、不可重复读、幻读的现象。
那么如果避免上述现象发生呢?

有两种方案:

  1. 读操作使用MVCC,写操作加锁
  2. 读操作和写操作都加锁

在某些业务场景中不允读取记录的旧版本,而是每次都必须读取记录的最新版本,例如在银行存款的事务中,需要先读账户余额,然后加上本次存款余额再写回数据库,在将账户余额读取出来后,就不想再让别的事务访问该余额,直到本次存款事务执行完成后,因此在读取的时候也就需要对其进行加锁操作,排队执行

1.4 一致性读

事务利用MVCC进行的读取操作称为一致性读,所有普通的select 语句在READ COMMTIED和REPEATABLE READ隔离级别下都是一致性读,一致性读不会对表中的任何记录进行加锁操作

1.4 共享锁和独占锁

在使用加锁的方式来解决可能出现的一致性问题时,既要允许读-读情况不受影响,又要使读-写操作相互阻塞,因此在MySQL中对锁进行了分类:

  • 共享锁:简称S锁,在事务要读取一条记录时,需要先获取改记录的S锁
  • 独占锁:也叫排他锁,X锁,在事务要改动一条记录时,需要先获取该记录的X锁

当事务T1获取到一条记录的S锁,那么其他事务只能获取到该记录的S锁而不能获取到X锁
当事务T1获取到一条记录的X锁,那么其他事务无法获取到该记录的任何锁

在这里插入图片描述

对读取的记录加S锁:

select .... lock in share mode;

对读取的记录加X锁:

select .... for update;

1.5 多粒度锁

上面提高到的锁都是针对一条记录的,可称为行级锁或行锁,锁的粒度比较细,一个事务也可以在表级别进行加锁,称为表锁。对一个表加锁,会影响表中的所有记录,表的粒度比较粗,同时,给表加的锁也可分为共享锁S和独占锁X锁

  • 一个事务对表加了S锁,其他事务可以继续获得该表的S锁和表中某些记录的S锁,不可以获取该表的X和记录的X锁
  • 一个事务给表加了X锁,其他事务不能记录获取该表中的任何锁

但这其中存在两个问题:

  • 当要对表加S锁时,我们要确保表中所有的记录没有被加X锁,如果有,则要等到行级X锁被释放后,再加表级S锁
  • 当要对表加X锁时,我们要确保表中所有的记录没有被加X锁或S锁,如果有,则要等行级锁X或S被释放后,再加表级X锁

【如何判断当前表中是否有关于记录的S锁或X锁呢?】

InnoDB给出的解决方案是,又提出了两种锁,分别是意向共享锁和意向独占锁

  • 意向共享锁:当事务要对表中记录加S锁时,需要先对表加一个IS锁
  • 意向独占锁:当事务要对表中记录加X锁时,需要先对表加一个IX锁

这样,在后面有其他事务想加表级S锁时,首先检查是否有关于表的IX锁,如果有,等待该IX锁被释放后,对整个表加S锁
如果其他事务想加表级X锁时,首先检查是否有关于表的IS锁或IX锁,如果有,等待被释放后,对整个表加X锁

小结:

IS 锁、 IX 锁是表级锁 它们的提出仅仅为了在之后加表级别的 锁和 锁时
可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录:也就是说其实 IS 锁和 IX 锁是兼容的 IX 锁和 IX 锁是兼容的

1.6 MySQL中的行锁和表锁

InnoDB中是支持表锁和行级锁的,但对于MyISAM、MEMORY、MERGE这些存储引擎来说,他们只支持表级锁,而且这些存储引擎并不支持事务

二、InnoDB中的锁

InnoDB存储引擎中既支持表级锁,也支持行级锁,表级锁粒度粗,占用资源少;但有时我们只需锁住几条记录,行级锁粒度细,可以实现更精准的并发控制,但是占用的资源较多

2.1 InnoDB中的表锁

  • 表级S锁、X锁

在对某个表执行增删改查语句时,InnoDB存储引擎不会为这个表添加表级别的S锁或X锁;在对某个表执行-些诸如 ALTER ABLE DROPTABLE DDL 语句时 其他事务在对这个表并发执行诸如 SELECT, INSERT,DELETE UPDATE 等语句,会发生阻塞.这个过程是通过server层使用一种称为元数据锁的东西来实现的,一般情况下也不会使用InnoDB存储引擎自己提供的表级S锁和X锁

  • 表级别的IS锁和IX锁

  • 表级别的AUTO-INC锁

在MySQL中,可以为某个列添加AUTO-INCREMENT的属性,之后在插入记录时,可以不指定该列的值,系统会自动赋予递增的值,实现系统自动赋予递增值有两种实现方式:

  • 采用AUTO-INC锁,在插入语句时加一个表级的AUTO-INC锁,然后为每条待插入的记录分配递增值,都插入结束后,把AUTO-INC锁释放掉
  • 采用一个轻量级锁,为插入语句生成AUTO-INCREMENT修饰的列的值时获取这个轻量级锁,生成完自增值后将轻量级锁释放掉,而不需要等到整个插入语句执行后才释放锁

2.1 InnoDB中的行级锁

在InnoDB中,行级锁才是重点,行级锁被分成了各种类型,换句话说,即使对同一条记录加行锁,如果记录的类型不同,起到的功效也是不同的。
为了后面方便举例,先创建一张表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • Record Lock

Record Lock仅仅是把这一条记录锁上,并没有什么花样,比如现在要将number为8 的记录加一个记录锁,如果这个记录锁是S锁,那么其他事务也可以继续获取该记录的S记录锁,如果这个记录锁是X锁,那么其他事务不可以获取该记录的S锁或X锁

  • Gap Lock

前面我们提到过,MySQL在REPEATABLE READ隔离级别下可以很大程度避免幻读现象发生,解决方案有两种:使用MVCC和加锁,但这里的加锁有一个问题就是在事务第一次执行读取操作的时候,那些幻影记录还不存在,无法给这些幻影记录加上锁,Gap Lock就是为了解决这个问题的,加入我们现在要为number为8的这条记录加锁,我们来看gap锁的效果:
在这里插入图片描述
为number为8的记录加gap锁,意味着不允许别的事务在number值为8的记录前面的间隙插入新的记录,实际上就是不允许在number列的值在区间(3,8)的新记录不允许立即插入。比如现在有另一个新事务想要插入number为4的记录,首先要定位到4的下一条记录,就是8这条记录,发现8这条记录上有个gap锁,就会阻塞插入操作,直到拥有gap锁的事务提交之后释放锁


gap锁的作用仅仅是为了防止插入幻影记录而提出的,而且如果对一条记录加了gap锁,并不影响继续对该记录加record lock锁

【如何加gap锁才能组织其他事务向(20,正无穷)这个区间插入记录呢?】

不要忘了,在我们的页面中存在两条伪记录,分别是Infimum(表示该页面中最小的记录)和Supermum(表示该页面中最大的记录),因此为了组织向(20,正无穷)这个区间插入记录,我们可以为Supermum这条记录加gap锁

  • Next-Key Lock

有时候我们既想锁住某条记录,又想阻止其他事务在该记录前面的间隙插入新纪录,Next-Key 可以起到上述效果

  • Insert Intention Lock

一个事务在插入一条记录时,需要判断插入位置是否已经被别的事务加了gap锁,如果有,则插入操作需要等待,直到gap锁被释放,InnoDB中规定,事务在等待时也需要在内存中生成一个锁结构,表明有事务想在某个间隙中插入新纪录,但是现在处于等待状态,这中锁被称为插入意向锁

比如现在事务T1为number为8的记录加了gap锁,然后T2和T3分别想插入一条number为4,5的记录,在这里插入图片描述

当T1提交后,gap锁被释放,此时T2和T3都能获取到相应的插入意向锁,也就是说他俩的插入操作不会相互阻塞

  • 隐式锁

隐式锁的本质是,能先不加锁就不加锁,一个事务对新插入的记录可以不显示的加锁,但是由于trx_id的存在,相当于加了一个隐式锁,别的事务想要对这条记录加S锁或者X锁时,由于隐式锁的存在,会先帮助当前事务生成一个锁结构,然后自己再生成一个锁结构,最后进入等待状态


因此,可以看出,隐式锁起到了延迟生成锁结构的用处,如果别的事务在执行过程中不需要获取与该隐式锁相冲突的锁,就可以避免在内存中生成锁结构

三、语句加锁分析

我们现为hero表的name列建立一个索引,如下图:
在这里插入图片描述

3.1 普通的select 语句

在不同的隔离级别下,普通select语句的表现:

  • READ UNCOMMITED隔离级别下,不加锁,直接读取记录的最新版本,可能出现脏读、不可重复读和幻读
  • READ COMMITED隔离级别下,不加锁,每次执行select语句时,生成一个ReadView,避免了脏读现象,但可能出现不可重复读和幻读
  • REPEATABLE READ隔离级别下,不加锁,只在第一次执行select语句时生成一个ReadView,这样将脏读、不可重复读、幻读避免了

【下面这种情况,导致在REPEATATABLE READ隔离级别下出现了幻读】

在这里插入图片描述
T1 在执行select时生成一个ReadView,之后T2插入一条记录后并提交,之后T1修改了这条新插入的记录,导致这条新纪录的trx_id是事务T1的id,之后T1在执行select时,就可以查到这条新插入的记录了,也就出现了幻读

3.2 锁定读语句

select ... lock in share mode;
select ... for update;
update ...
delete ...

上述四条语句,在操作记录时,都需要先获取到对应记录的锁
接下来,我们需要先了解两个概念:匹配模式和唯一性搜索

  • 匹配模式

在使用索引执行查询时,查询优化器首先会生成若干个扫描区间,针对每一个扫描区间,都可以在该扫描区间中定位到第一条记录,然后沿着这条记录所在的单向链表访问到该区间内的其他记录,直到某条记录不在扫描区间内。如果被扫描的区间是一个单点扫描区间,我们就可以说此时的匹配模式为精准匹配,例如,我们为某个表的a,b列建立联合索引

(1) 如果扫描区间是a=1,这就是单点扫描区间,也是精准匹配
(2) 如果扫描区间是a=1,b=1,这也是单点扫描区间,也认为是精准匹配
(3) 如果扫描区间是(1,正无穷),这就是不精准匹配

  • 唯一性匹配

如果在扫描某个扫描区间的记录前,就能事先确定该扫描区间内最多只有一条记录的话,那么这种情况称作唯一性匹配

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值