暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL真正考验实力的面试题: select for update如何加锁?

运维路书 2023-09-25
93


面试八股文,是近年来互联网行业中频繁被提及的词。行业内卷严重,从业人员多,素质也是良莠不齐。因此出现了很多面试造火箭,工作拧螺丝的现象。

其实面试这个一步,不仅考验应聘者同时也考验着面试官。能不能提出一个优秀的问题,也显示出面试官的能力。一味地抠一些边边角角地原理来提升面试难度完全没有意义,一定是从实际应用中出发,提出一些符合日常使用地技能问题才能筛选出合适地应聘者。

这个系列将总结出一些,各人认为比较符合实际应用,同时也能考察水平地问题,分享给大家。请根据自身情况,按需取用。仅代表个人观点,如果有不同意见欢迎理性讨论。




先来一个跟MySQL锁相关的题来尝尝鲜,个人认为如果这道问题能回答上来,基本上关于MySQL加锁这方面地理解就很透彻了。实际应用中也不会出现太大的问题。


面试官

select for update 在MySQL内部是如何加锁的?


标准的字少事大,题面很简单,就一句话。实际回答起来却涉及了几乎所有的MySQL行级锁相关的知识。我为这个问题画了一个思维导图,可以考虑从这几个方面逐一的回答。


这张图,基本上涵盖了MySQL行级锁加锁相关的所有可能情况。

接下来逐一的进行说明,这个问题回答起来估计得20分钟时间。


MySQL行级锁的分类


根据不同事务隔离级别,行锁的类型也有所不同,通常我们只会用到两种隔离级别RC(读提交)和RR(可重复读)

 

在RC隔离级别下,行锁的种类只有 记录锁(Record Lock),也就是只锁住一条记录。

在RR隔离级别下,行锁的种类除了记录锁还增加间隙锁和临键锁,目的是为了防止幻读,因此行锁种类分为三种

  • Record Lock,记录锁,只锁定一条记录

  • Gap Lock,间隙锁,锁定一个范围,但不包含记录本身

  • Next-Key Lock,Record Lock+Gap Lock的组合,锁定一个范围,同时也锁定记录本身。






读提交


在RC的隔离级别下,只有记录锁这一种类型。有索引和无索引的加锁方式有一定的差别,先说结论,再做实验来证明。


结论


  • 有索引,首先加一把表级的意向排他锁。然后无论是主键索引、唯一索引、普通二级索引都会给自身索引加一个记录锁。最后,查询到的记录会在对应的主键上加一把锁。

  • 无索引,扫描全表顺序加锁,再判断记录是否符合要求,符合就保留锁,不符合就释放锁。


通过实验的方式证明以上结论


有索引


首先建一张实验表,包含主键索引,唯一索引,普通二级索引



1






主键索引


根据图中的实验可以看出,对主键索引的扫描,加了两把锁:

  • 表级,意向排他锁

  • 行级,主键的记录锁


2






唯一索引



如图所示:唯一索引查询,加了三把锁:


  • 表级,意向排他锁

  • 行级,唯一索引自身的记录锁

  • 行级,查询到的数据对应的主键索引的记录锁


3






普通索引




如图所示:普通索引查询,加了三把锁:


  • 表级,意向排他锁

  • 行级,普通索引自身的记录锁

  • 行级,查询到的数据对应的主键索引的记录锁


无索引


证明无索引的情况下,是全表顺序扫描的,首先建表。



发起事务A


 除了表级锁意向排他锁以外,还有就是满足查询条件的记录上的主键加了一把记录锁。


发起事务B

    select * from t1 where name='a' for update;
    //阻塞


    如图所示:通过THREAD_ID可以判断出绿色框为事务B,存在三把锁,除了意向排他锁以外。首先,扫描了id=1的记录并给主键加了记录锁。接下来,扫描id=2的数据,在准备对主键加锁时,出现了锁等待,因为事务A持有id=2的记录锁。


    发起事务C

      select * from t1 where name='c' for  update; 
      //阻塞


      如图所示:同样从id=1的记录开始扫描并加锁,由于事务B持有着id=1的记录的锁,所以,事务C处于锁等待的状态。







      可重复读


      可重复读的隔离级别下,会比较复杂,需要考虑多种情况,还是先说一下结论


      结论


      可重复读的隔离级别下行锁的加锁的基本单位是 Next-Key Lock,但是,在不同的索引类型以及是否使用索引的不同情况下,next-key lock会相应的退化成记录锁或间隙锁

      • 主键索引

        • 查询记录存在,在索引树上定位到这一条记录后,将该记录的索引中的 next-key lock 会退化成记录锁

        • 查询记录不存在,在索引树找到第一条大于该查询记录的记录后,将该记录的索引中的 next-key lock 会退化成间隙锁

      • 唯一索引

        与主键索引大致相同,但是当查询记录存在时,会在该记录的主键索引上多加一把记录锁。

      • 普通索引

        • 查询记录存在,由于不是唯一索引,索引字段可能存在相同值。因此,非唯一索引要直到扫描到第一个不满足查询条件的记录才停止扫描。

          • 扫描过程中对于扫描到的普通索引记录加next-key lock

          • 对于第一个不满足条件的普通索引记录加锁将退化成间隙锁

          • 符合查询条件的记录的主键索引上加一把记录锁

        • 查询记录不存在,扫描到第一个不满足查询条件的记录是,该记录的索引退化成间隙锁。由于查询记录不存在,因此不会在主键上加锁

      • 无索引

        如果查询条件没有用到索引列或者索引失效,会导致全表扫描,在每条记录的主键索引上加一把next-key lock。这就相当于锁住了全表,其他事务对该表的增、删、改 都会被阻塞。


      接下来,通过几个实验,来验证一下上述结论。


      有索引


      有索引的查询,根据索引类型的不同,加锁也是不相同的,主要分为唯一索引和普通索引两大类


      01


      主键索引




      当用主键索引进行等值查询的时候,查询的记录存不存在,加锁的规则也会不同:


      • 记录存在


      如图所示,当查询记录存在时,查找到该记录后,将next-key lock 退化成记录锁


      ?

      为什么Next-Key Lock会退化成记录锁?



      解答

      这里涉及一个MySQL加锁的原则:最小可用原则,在避免幻读的基础上够用就行,尽量减少锁的范围,提高并发性能。


      只要保证结果集中这条查询到的记录不被修改,就可以满足要求,所以只需要记录锁就足够了。

      • 记录不存在



      如图所示:在id=5的记录上加了一把间隙锁,也就是说在第一个不满足查询条件的记录上的next-key lock退化成了间隙锁(1,5)。按照最小可用原则,只要锁住1到5之间的间隙,就可以满足前后两次查询不会出现幻读。


      02


      唯一索引




      当查询记录存在时,会在该记录的主键索引上多加一把记录锁。



      03


      普通索引




      在分析普通索引的锁之前,先弄清楚一个问题

      普通索引树如何存放记录?


      普通索引树是按照普通索引值的顺序存放的,在相同的普通索引值下,再按照主键顺序存放

      因此实验表的普通索引是按照age顺序存放的:

      • 记录存在


      如图所示:总共加了4把锁,除了意向排他锁以外,还加了(21,22]的next-key lock,id=10的主键记录锁,(22,30)的间隙锁。

        • 由于普通索引是非唯一索引,可能存在多个age=22的值,首先会在普通索引上加一把next-key lock,以保证age=22的记录不被修改或删除。

        • 继续向后扫描,扫描到数据age=30,这行数据作为第一个不满足查询条件的记录,会退化成间隙锁。因为age=30这行数据的修改不会影响查询语句的第二次查询。

        • 由于age=22这条记录存在,所以,id=10的主键也会加一把记录锁,以保证其他事务无法修改和删除id=10这条记录。

      • 记录不存在


      如图所示:由于age=24这条记录不存在,因此向后扫描到第一个不满足条件的记录后,停止扫描,并且将该普通索引的next-key lock退化成间隙锁



      无索引


      如图所示,无索引的情况下每条记录的主键索引都加了next-key lock,也就是相当于表锁。



      文章转载自运维路书,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

      评论