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

一、锁

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为45的记录在这里插入图片描述

当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插入一条记录后并提交之后T2修改了这条新插入的记录导致这条新纪录的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=1b=1这也是单点扫描区间也认为是精准匹配
(3) 如果扫描区间是(1,正无穷)这就是不精准匹配

  • 唯一性匹配

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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: mysql

“MySQL(九):锁、表锁、行级锁、Gap Lock、Next-Key Lock” 的相关文章