浅谈Innodb的锁实现

目录

一、知识准备

    1.1、lock和latch

    1.2、主键索引和辅助索引

    1.3、事务的隔离级别

二、Innodb行锁的三种算法

    2.1、主键索引如何加锁

    2.2、辅助索引(非唯一索引)如何加锁

三、发生死锁的几种情况
四、个人小结


一、知识准备

    1.1、lock和latch

        lock::lock的对象是事务,用来锁定的是数据库中的对象,如表,页,行。(这个概念很重要,有助于理解后续的加锁行为

        latch:latch是为了保证并发线程操作临界资源的正确性(本文可以忽略这个概念)

    1.2、主键索引和辅助索引

        主键索引(聚集索引/聚簇索引): 每张表按照主键构造一棵b+树,叶子节点存放的即为整张表的行记录数据

        辅助索引:按辅助索引的键值构建b+树,叶子节点的索引行中,除了包含索引的键值外,还包含一个书签(bookmark),指向主键索引上该行的完整记录

     1.3、事务隔离级别

        此处不再赘述,本文会使用mysql默认的隔离级别,REPEATABLE READ(RR)

二、Innodb行锁的三种算法

    Innodb有三种行锁的算法,分别是:

    1⃣️、Record Lock:单个行记录的锁

    2⃣️、Gap Lock:间隙锁,锁定一个范围,但不包含记录本身

    3⃣️、Next-Key Lock:Record Lock + Gap Lock

     下面我们分析下,不同场景下的加锁情况,以及为什么要用对应的加锁算法。

    2.1、主键索引如何加锁(mysql默认事务隔离级别:RR)

    delete from t where id = 7;   这种情况加锁很简单,由于主键唯一,所以只需要在主键上id=7加X锁即可。(Record Lock)

    

                                            图1-1

    2.2、辅助索引(非唯一索引)如何加锁(mysql默认事务隔离级别:RR)

    select * from t where id=5 for update;    该场景下的加锁如下图所示。

 

                                                                    图1-2       

    从图中可以看出,本文除了在辅助索引和对应的主键索引上加了两个行记录的X锁(Record Lock)外,还在辅助索引上加了3个GAP锁。那么为什么要用GAP锁呢?其实这个是Innodb为了解决幻读问题(phantom problem)。大家想一下,为了保证一个事务中的连续两次读(如:select * from t where id=5 for update)结果相同,那么就要防止上锁期间,有id=5的记录插入表中,由于id是非唯一索引,考虑到b+树索引的连续性,所以,能够插入id=5记录的,只有(3,5),(5,5),(5,9)三个区间,即上图三个红箭头的位置范围(上文1.1中提到lock的对象是数据库中的对象,所以GAP锁的的边界会加在具体的索引记录上,所以id=5的话,那就会在3和9之间加上GAP锁。

    网上有些资料,包括我看的一些书上,对于Next-Key Lock的描述要么模凌两可,要么浅尝则止。这样的描述很容易误导刚刚入坑的同学。大部分的资料描述的区间锁范围都是左闭右开。比如上文中select * from t where id=5 for update;那么GAP LOCK的范围就是 (3,5],(5,5],(5,9]。按照这个描述,那么id=3的记录不可插入,id=9的记录可以插入。那么,事实是不是这样的呢?

    下面我们一起来尝试一下,直接上图:

    首先我们看下表中的记录(上文中的例子):

            

    step1: sessionA ,发起一个事务,给记录上锁

                

    setp2: sessionB,发起一个事务,插入边界值。

            

    插入id=3的两条语句,一条失败了,一条成功了,情况好像跟我们想的有点不一样。。。疑问 。 继续上图

            

   插入id=9的两条语句,又是一条失败了,一条成功了。这个情况,和Next-Key Lock的定义,左闭右开,貌似不一样啊委屈

   下面,我们一起来分析下,到底是为什么?

    要想搞明白这个问题,还是得提到1.1章节中,加粗加红的那个lock的定义,它是加在索引上的。再结合索引的连续性,那么这个问题就好理解了大笑

    请大家把目光回到图1-2,对于辅助索引叶子节点上的排序,可以简化为该图中的样式。所以,GAP LOCK的范围是((3,d) ,(5,c)),((5,c) ,(5,f)),((5,f) ,(9,g)),RECORD LOCK锁定的是(5,c)和(5,f)。所以对于边界值的插入就很清楚了哈

场景1、

mysql> insert into t(id,name) values(3,'f');

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

(3,f) 在((3,d) ,(5,c))之间,不能插入。  

场景2、 

mysql> insert into t(id,name) values(3,'b');

Query OK, 1 row affected (0.00 sec)

(3,b) 在 ( (3,d) , (5,c))的左侧,可以插入。  

场景3、

mysql> insert into t(id,name) values(9,'e');

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

(9,e) 在((5,f) ,(9,g))之间,不能插入。

场景4、

mysql> insert into t(id,name) values(9,'h');

Query OK, 1 row affected (0.00 sec)

(9,h) 在((5,f) ,(9,g))右侧,可以插入。


三、发生死锁的几种情况
        3.1、显示的互相等待(简单场景)
        
        3.1、隐式的互相等待(稍复杂场景)
        还记得图1-2中的场景吗?辅助索引上加锁后,会在对应的主键索引上加锁。那么当一张表中有多个辅助索引的时候,是不是会初现死锁呢?这个场景作为思考题,大家自己思考下哈 大笑

四、个人小结
        本章的分享,其实就是我学习这块知识遇到问题,以及解决问题的过程。想要理解innodb的加锁原理,必须要理解b+树和索引。当然了,遇到难题,不要轻易放弃哈,有时候用下逆向思维,想下作者发明这个技术,是为了解决什么问题 大笑



    



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值