锁的种类

image.png
按锁的模式分: 共享锁(s) 排它锁(x) 意向共享锁(is) 意向排它锁(ix)
按锁的算法分: 记录锁(锁当前记录) 间隙锁(锁他的前后间隙)(解决幻读,RR隔离级别下才会有) 临键锁(锁当前记录和他的前面的间隙)

个人理解总结:
锁的目的都是当前读,读最新的版本select lock in share mode (共享锁)、select for update (排他锁)、update (排他锁)、insert (排他锁)、delete (排他锁)、串行化事务隔离级别都是当前读。

1.锁如果锁的是行就是行锁 锁的是表就是表锁 ,锁的目的是共享,表明我在读就是s锁,目的是独占表明我要对这行或者这个表进行写操作,独占这个表或者行,
表明我打算锁这个某一行,先在表上加一个意向锁(is,ix),意向锁因为是表明意愿所以可以在表上随便加不存在冲突问题. s锁 x锁存在兼容性问题, 当这条记录不存在x
锁时随便加,但要对这条记录加x锁时需要等s锁全部解除, 当这条记录有x锁时s x都不能加,只能等待x锁解除

2.只锁这一行用的是记录锁 锁只他的前后就是间隙锁(mysql解决幻读的方式),锁这行和他的前面就是临键锁(RR隔离级别下,以防止幻读的发生),串行化事务就不存在那些问题,直接锁的表(加的表级共享锁,自己操作读到的行,其他事务可以继续读其他没被此事务读到(锁住)的行,但不能给这个表的行或表加X锁)
锁本质是锁的索引,所以不走索引都锁表

3.mvcc按理说是读写并不冲突的,但mysql中对写操作都加了锁,并没有完全利用mvcc的特性,所以mysql的mvcc仅限于不阻塞读,提示读的性能(一致性读,快照读),但写依旧是加锁,是伪mvcc

写操作的锁

平常所用到的写操作无非是 DELETE、UPDATE、INSERT 这三种:
DELETE:
对一条记录做 DELETE 操作的过程其实是先在 B+树中定位到这条记录的位置,
然后获取一下这条记录的 X 锁,然后再执行 delete mark 操作。我们也可以把这
个定位待删除记录在 B+树中位置的过程看成是一个获取 X 锁的锁定读。
INSERT:
一般情况下,新插入一条记录的操作并不加锁,InnoDB 通过一种称之为隐
式锁来保护这条新插入的记录在本事务提交前不被别的事务访问。当然,在一些
特殊情况下 INSERT 操作也是会获取锁的,具体情况我们后边再说。
UPDATE:
在对一条记录做 UPDATE 操作时分为三种情况:
如果未修改该记录的键值并且被更新的列占用的存储空间在修改前后未发
生变化,则先在 B+树中定位到这条记录的位置,然后再获取一下记录的 X 锁,
最后在原记录的位置进行修改操作。其实我们也可以把这个定位待修改记录在
B+树中位置的过程看成是一个获取 X 锁的锁定读。
如果未修改该记录的键值并且至少有一个被更新的列占用的存储空间在修
改前后发生变化,则先在 B+树中定位到这条记录的位置,然后获取一下记录的 X
锁,将该记录彻底删除掉(就是把记录彻底移入垃圾链表),最后再插入一条新
记录。这个定位待修改记录在 B+树中位置的过程看成是一个获取 X 锁的锁定读,
新插入的记录由 INSERT 操作提供的隐式锁进行保护。
如果修改了该记录的键值,则相当于在原记录上做 DELETE 操作之后再来一
次 INSERT 操作,加锁操作就需要按照 DELETE 和 INSERT 的规则进行了。

隐式锁(insert操作)

Insert Intention Locks
我们说一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了所谓的 gap 锁(next-key 锁也包含 gap 锁,后边就不强调了),如果有的
话,插入操作需要等待,直到拥有 gap 锁的那个事务提交。 但是 InnoDB 规定事务在等待的时候也需要在内存中生成一个锁结构,表明
有事务想在某个间隙中插入新记录,但是现在处于等待状态。这种类型的锁命名 为 Insert Intention Locks,官方的类型名称为:LOCK_INSERT_INTENTION,我们也
可以称为插入意向锁。可以理解为插入意向锁是一种锁的的等待队列,让等锁的事务在内存中进行
排队等待,当持有锁的事务完成后,处于等待状态的事务就可以获得锁继续事务 了。
隐式锁
锁的的维护是需要成本的,为了节约资源,MySQL 在设计提出了了一个隐式锁的概念。一般情况下 INSERT 操作是不加锁的,当然真的有并发冲突的情况
下,还是会导致问题的。所以 MySQL 中,一个事务对新插入的记录可以不显式的加锁,但是别的 事务在对这条记录加 S 锁或者 X 锁时,会去检查索引记录中的 trx_id 隐藏列,然
后进行各种判断,会先帮助当前事务生成一个锁结构,然后自己再生成一个锁结构后进入等待状态。但是由于事务 id 的存在,相当于加了一个隐式锁。
这样的话,隐式锁就起到了延迟生成锁的用处。这个过程,我们无法干预,是由引擎自动处理的,对我们是完全透明的

锁的内存结构

所谓的锁其实是一个内存中的结构,在事务执行前本来是没有锁的,也就是
说一开始是没有锁结构和记录进行关联的,当一个事务想对这条记录做改动时,
首先会看看内存中有没有与这条记录关联的锁结构,当没有的时候就会在内存中
生成一个锁结构与之关联。比方说事务 E1 要对记录做改动,就需要生成一个锁
结构与之关联。锁结构里至少要有两个比较重要的属性
image.png

当事务 T1 改动了条记录后,就生成了一个锁结构与该记录关联,因为之前
没有别的事务为这条记录加锁,所以 is_waiting 属性就是 false,我们把这个场景
就称之为获取锁成功,或者加锁成功,然后就可以继续执行操作了。
在事务 T1 提交之前,另一个事务 T2 也想对该记录做改动,那么先去看看有
没有锁结构与这条记录关联,发现有一个锁结构与之关联后,然后也生成了一个
锁结构与这条记录关联,不过锁结构的 is_waiting 属性值为 true,表示当前事务
需要等待,我们把这个场景就称之为获取锁失败,或者加锁失败,或者没有成功
的获取到锁:
image.png
在事务 T1 提交之后,就会把该事务生成的锁结构释放掉,然后看看还有没
有别的事务在等待获取锁,发现了事务 T2 还在等待获取锁,所以把事务 T2 对应
的锁结构的 is_waiting 属性设置为 false,然后把该事务对应的线程唤醒,让它继
续执行,此时事务 T2 就算获取到锁了。
这种实现方式非常像并发编程里的 CLH 队列。
(并不是对一条记录加锁就要创建一个锁内存结构,基本上来说,同一个事务里,同一个数据页面,同一个加锁类型的锁会保存
在一起)

查看事务加锁情况

可以通过 show engine innodb status\G(需要修改系统变量 innodb_status_output_locks为 ON)

加锁补充:
https://blog.csdn.net/yeyinzhu3211/article/details/122996620

加锁单位是 next-key lock ( 间隙锁 + 行锁)

原则1:加锁的基本单位是next-key lock。next-key lock是前开后闭区间。
原则2:查找过程中访问到的对象才会加锁。
原则3:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁。
原则4:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁。
原则5:唯一索引上的范围查询会访问到不满足条件的第一个值为止。(在 mysql 45 讲中有说明,丁老师认为这是一个 bug,且在 8 版本的时候已经修复,待会也会举例说明)

个人总结:
加锁的基本单位是next-key lock.(原则1), 加锁本质上是走的是索引,不走索引直接锁表(原则2) ,当能唯一确定一行时(唯一索引,主键等值查询,且能满足条件数据存在,不满足则会变成间隙锁)临间锁就会退化为行锁,只锁这一行;(原则3)当不能唯一确定一行时,如果是普通二级索引的等值查询,不能确定前后,因此临间锁会升级为间隙锁,锁前后.如果是范围查询如 ~~deletefrom~~ t ~~where~~ c ~~>~~15;则还是临间锁

本质上锁的基本单位是临间锁,访问到的对象就会加锁,走的是索引,锁退化为行锁只锁一行,还是不升级或者是升级为间隙锁取决于索引能定位到的区间中能确定的行,