mysql面试关键知识点:锁机制

比如说一个新的数据库在设计的时候,可以设计有以下4个级别的锁:

  • 数据库级别的锁
  • 表级别的锁
  • 页级别的锁
  • 记录级别的锁

实际上innodb没有数据库级别的锁,也没有页级别的锁,其只有表级别的锁和记录级别的锁。

加锁是从上往下(表到记录),一层一层的加锁

释放锁就是从下向上(记录到表)逐层释放的

innodb中的锁

  • S 行级共享锁 record-lock(包含record lock + gap lock)
  • X 行级排他锁 record-lock(包含record lock + gap lock)
  • IS 意向锁
  • IX 意向锁
  • AI 自增锁 Auto_Increment Lock

锁的兼容性
在这里插入图片描述

显式锁 隐式锁

如for update, lock in share mode都是加显式锁,而增删改这种,都是隐式锁

show engine innodb status,显示的锁信息都是显式锁(已经创建了内存对象的),隐式锁是不在内存中的。


意向锁

举例来说:

事务1:比如给rowid=8的记录加X锁,其实际的执行过程是表级别IX,之后才是给rowid=8的记录加X锁

事务2:如现在rowid=6的记录加S锁,同样的实际的执行过程是表级别IS,之后才是给rowid=6的记录加S锁

意向锁之间是全部兼容的,因为意向锁表示的是我下一层级要加什么锁,而并不代表我当前层要加什么锁,所以IS,IX都是互相兼容的。可以叠加很多的意向锁。

现在有事务3:比如给rowid=8的记录加S锁,同样的实际的执行过程是表级别IS,这些都没有问题,但在给rowid=6的记录加S锁的时候,会发现记录已经加了X锁,于是要等待,等待事务1提交,其才会再加S锁
在这里插入图片描述
为什么要设计意向锁呢?似乎并没有什么实际作用,并且使加锁过程更复杂了。

innodb存储引擎支持多粒度锁定(multi-granular lock),这种锁定允许事务在行级上的锁和表级上的锁同时存在。为了支持在不同粒度上进行加锁操作,InnoDB 存储引擎支持一种额外的锁方式,称之为意向锁( Intention Lock )。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度(fine granularity)上进行加锁。

假设现在没有意向锁,我想直接对表进行加锁,那这张表怎么知道其下面有没有记录被更新呢?如果有记录被加了X锁,那就不能对这张表加S锁。

如果没有意向锁,那我想给这张表加S锁或X锁,那怎么实现呢?因为我需要知道下面记录加锁的情况,就很难实现。当游标向下扫,可能有一条记录又插入到游标已经扫描过的部分,所以实际上无法判断到底能不能对加S锁或X锁。

这样一层一层加锁是不是会很慢,很麻烦?
实际上意向锁都是兼容的,而且意向锁都是在内存中的,速度很快,一个cpu一秒钟可以做非常多的指令

AI自增锁

在InnoDB存储引擎的内存结构中,对每个含有自增长值的表都有一个自增长计数器(auto-increment counter)。当对含有自增长的计数器的表进行插入操作时,这个计数器会被初始化。

用参数innodb_autoinc_lock_mode来控制释放锁的时机。

问题:mysql的自增值是如何保存的呢?
每次启动之后,取一个max值,放到内存cache中(每个表一个),如果cache中有这个值,每次就在cache中自增了。如果cache中没有,那么再去查表,获取一个最大值,再放到cache中。

行锁

对记录加锁,可能使用下面3种方式中的一种:

  • record lock

    单个行记录上的锁

  • gap lock

    锁定一个范围,但不包括记录本身

    比如说你的记录有10,30,50,gap锁意思类似于把10到30这个范围锁定了,你无法向其中插入20

    如果你执行的是 记录<30,那其锁定的是负无穷到10,以及10到30,这样就不会发生幻读问题了

    如果你执行的是 记录<30 && >20,那么其锁定的是10到30这个范围

  • next-key lock

    gap lock + record lock,锁定一个范围,并且锁定记录本身

    也就是你不能对30这条记录update了,而gap锁是可以对30这条记录update的

总结:

  1. RR隔离级别下的锁默认为 Next-Key Lock

  2. RR隔离级别下会对游标打开的所有的记录进行加锁

  3. RC隔离级别下没有GAP锁,只是锁住记录本身

对查询范围内的记录进行了锁定,于是就解决了幻读问题

但可以看到next-key锁的代价还是非常大的,可以看到比如记录有10,13,20,查询记录小于等于13,要锁住负无穷到20这么大的范围

next-key lock降级为record lock

innodb会对next-key lock进行优化,优化为record lock

当索引含有唯一约束时,并返回的结果为1条

next-key lock降级为record lock

可以降级为Record Lock 是因为返回的记录具有唯一性,不会存在幻读问题。

next-key lock不能降级的情况

Gap locking is not needed for statements that lock rows using a unique index to search for a unique row. (This does not include the case that the search condition includes only some columns of a multiple-column unique index; in that case, gap locking does occur.)

当使用unique index获取唯一一条row记录的时候,gap lock不会被使用,但如果返回的列中只包括多列唯一索引中的部分列,那么gap lock仍是会使用的。

锁住所有记录(类似表锁的效果)

在RR 的隔离级别下,当查询的列没有索引时,next-key lock会锁住所有记录(负无穷 到 正无穷)

假设RR允许幻读,没有gap锁,会发生什么问题

如下图
在这里插入图片描述
如果允许幻读,没有gap锁,那么插入6是可以的。

那么master最后的结果就是6,而binlog里面记录的就是 insert 6,del<=7(先提交的在前面)

如果把日志传给slave,那么slave中记录为空了。即对于replication ,可能会导致主从数据的不一致。

再回想隔离性的定义,多个事务并行执行,一个事务所做的修改,对其他并行的事务是不可见的,(不管这个事务有没有提交)。好似是每个事务是串行执行的

而幻读,并没有实现每个事务是串行执行的

有了gap锁,那么其binlog一定是 del<=7,之后再insert 6

insert intention lock 插入意向锁

在gap锁里面,还有一个特殊的锁,insert intention lock

插入意向锁本质上就是个Gap Lock,只不过这个gap lock是允许插入的gap lock

为什么需要insert intention lock呢?

普通Gap Lock 不允许在(上一条记录,本记录) 范围内插入数据

插入意向锁Gap Lock 允许在(上一条记录,本记录) 范围内插入数据

插入意向锁的作用是为了提高并发插入的性能,多个事务同时写入不同数据至同一索引范围(区间)内,并不需要等待其他事务完成,不会发生锁等待

假设现在有记录10,30,50,70 ;且为主键,需要插入记录25 。

  1. 找到小于等于25的记录,这里是10

  2. 找到记录10的下一条记录,这里是30

  3. 判断下一条记录(为啥是对下一条记录判断呢,因为如果下一条记录加了锁,那么说明在这个gap上,可能会出现幻读问题)30 上是否有锁(如果有=25的情况,后面再讨论)

    • 判断30 上面如果没有锁,则可以插入
    • 判断30 上面如果有Record Lock ,则可以插入
    • 判断30 上面如果有Gap Lock / Next-Key Lock ,则无法插入,因为锁的范围是(10, 30) / (10, 30] ;在30 上增加insert intention lock (此时处于waiting 状态),当Gap Lock / Next-Key Lock 释放时,等待的事务(transaction)将被唤醒,此时记录30 上才能获得insert intention lock ,然后再插入记录25。

注意:一个事务insert 25 且没有提交,另一个事务delete 25 时,记录25上会有Record Lock

对比RR和RC隔离级别下的加锁情况

非索引列的等值查询在RC下与RR不同。

  • 在RR 的隔离级别下,当查询的列没有索引时,会锁住所有记录

  • 在RC下,只会对满足条件的聚集索引加record lock

当查询条件是二级索引列的等值查询时

  • RC 模式下,二级索引查询的记录上有一个记录锁, 对应的聚集索引上有一个记录锁
  • RR 模式下,二级索引查询的记录上有一个Next-Key Lock ,该记录的下一个记录上有一个Gap-Lock (二级索引);对应的聚集索引上有一个记录锁

锁的物理实现

每个事务对每个页,会有一个锁对象lock_rec_t,大小约为100字节

其通过位图来存放锁信息,内存占用少
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值