MySQL-浅析间隙锁

  • MySQL 锁有哪些

    • 从类型上来看,可以分为共享锁、排它锁

    • 从范围来看,可以分为表锁、行锁,间隙锁、页锁等。

    • 其中表锁中又有意向锁。

    • 以上锁根据存储引擎不同,生效的锁也不同。

  • 什么是间隙锁

    我们首先先定义下关于下面描述的前提场景,首先是基于InnoDB存储引擎,Repeatable Read 隔离级别下的。从名字我们可以猜测出,间隙锁,锁定的是某块间隙,但是锁定的是那块间隙呢?什么场景会触发间隙锁呢?网上有很多关于间隙锁的原理讲解,但有的描述不严谨,下面就带着疑问,跟随一起一步步验证下吧。(PS:下面描述的内容不全是间隙锁,当时验证的时候出了一些自认为很有意思的问题,就记了下来和大家分享,知晓的朋友请自动忽略)

    • 间隙锁锁定范围

      首先定义如下表

    CREATE TABLE `gap_lock`  (
     id bigint(20) NOT NULL,
     number int(11) NOT NULL,
     PRIMARY KEY (id) USING BTREE,
     INDEX idx_number(number) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;  

插入相应的数据

    INSERT INTO `gap_lock`(`id`, `number`) VALUES (1, 1);
    INSERT INTO `gap_lock`(`id`, `number`) VALUES (3, 3);
    INSERT INTO `gap_lock`(`id`, `number`) VALUES (5, 5);
    INSERT INTO `gap_lock`(`id`, `number`) VALUES (8, 8);   

锁定某行数据

select * from `gap_lock` where number = 3 for update;   

image-20190423152444434.png

此时,我们开启一个新的会话,验证下面的sql

    insert into `gap_lock` values(2,2);#阻塞
    insert into `gap_lock` values(4,4);#阻塞
    insert into `gap_lock` values(6,5);#非阻塞
    insert into `gap_lock` values(0,1);#非阻塞   

从上面的执行SQL情况可以看出,间隙锁锁定范围是number 数值(1,3)和 (3,5),开区间。我们再执行下下面的sql

insert into `gap_lock` values(2,1);#阻塞   

纳尼,不是开区间么?不包含number等于3和5 么?难道是不单单锁定二级索引number,还锁定了主键索引的范围,整个锁定范围ID是(1,3)和(3,5),number范围是(1,3)和(3,5)?如果是这样的话,那么怎么的一个判断逻辑呢?是如下的判断逻辑么

    boolean idLocked = (id > 1 && id < 3) || (id > 3 && id < 5);
    boolean numberLocked = (number > 1 && number < 3) || (number > 3 && number < 5);
    if(idLocked && numberLocked){
     return true;
    }   

我们继续验证

insert into `gap_lock` values(2,6);#非阻塞   

123.png

???首先可以确定的是上面的假想有问题,而且此时会有疑问,为什么(2,6)在第四行,不是默认按照id升序排序么,怎么会按照number排序,难道还和排序有关,此时一脸懵逼.jpg。我们继续验证

insert into `gap_lock` values(6,1);#阻塞   

234.png

(6,1)在第二行,确实出现在我们以前画的锁定的间隙范围内,看来首要问题就是要解决下排序问题了,搞清楚排序问题,应该问题就迎刃而解了。
我们来看看查询的执行计划

explain select * from `gap_lock`;   

345.png

通过执行计划,发现原来是使用了覆盖索引机制,怪不得排序不是按照主键进行的排序。我们回顾下innodb的索引结构。
456.png

树的内节点存储索引的key,叶子节点存储key和具体的数据信息,如果是二级索引,data是记录的主键值,如果是主键索引的话,data的是包含事务ID、回滚ID、版本号以及行数据信息等。
由于IO操作耗时,提高性能的可以通过减少磁盘的IO次数,目前这张表刚好只有两个字段,二级索引number其实可以覆盖所有的行数据信息,读取number索引可以达到查询全部字段的效果,相对来说,二级索引相较于主键索引存储更密集,磁盘IO读取次数更少。
知晓了排序的问题,那么间隙锁锁定范围就突然清晰了
467.png

在number这个索引上,排序是如上图,优先按照number进行排序,如果number相同,则按照id排序,大致判断逻辑如下

    Node<K,V> lockNode = new Node(3,3);
    Node<K,V> preNode = lockNode.pre;//(1,1)
    Node<K,V> nextNode = lockNode.next;//(5,5);
    /**
    * 校验是否被间隙锁锁住,不能执行插入操作
    * @param node 待插入的索引节点
    * @return true 表示不允许执行插入操作,false表示可以执行插入操作。
    **/
    public boolean checkGapLock(Node<K,V> node){
     //锁定的行是最后一行
     if(nextNode == null){
     if(node.key > preNode.key){
     return true;
     }else if(node.key == preNode.key && node.val > preNode.val){
     return true;
     }
     return false;
     }
     //锁定的是中间某行(该索引节点不一定存在)
     if(node.key > preNode.key && node.key < nextNode.key){
     return true;
     }else if(node.key == preNode.key  && node.val > preNode.val){
     return true;
     }else if(node.key == nextNode.key && node.val < next.val){
     return true;
     }else{
     return false;
     }
    }   
  • 怎样触发间隙锁的呢?
    网上很多描述,例如select * from table where index = xx for update,又或者删除某条不存在的记录等。

  • 二级索引&非唯一索引 ,如果某个叶子节点(若不存在则假设它存在)被添加了排它锁,那么就会在该叶子节点周围添加间隙锁,如果刚好其它会话要在间隙锁范围内创建索引,那么则会被挂起。

  • 主键索引& 唯一索引,如果要添加排它锁的节点存在,不会触发间隙锁,如果不存在,则触发间隙锁。

  • 为什么主键索引或者唯一索引与普通的索引在间隙锁上的机制有差别呢?我们先来看看什么是幻读和不可重复读。

    • 幻读和不可重复读从结果来看,都是针对一个事务中,多次读取的结果与目标结果不一致。从解决的目的上,是区分不了幻读和不可重复读的。那么从范围上去划分。

    • 从范围上区划分,分为指定行多次读取结果不一致,和范围读取结果不一致。

    • 指定行多次读取,为了解决结果不一致的问题,最简单的办法是加锁(悲观锁)和多版本控制(MVCC)(乐观锁)来实现。我(个人理解,可能不正确)称指定行多次读取结果不一致,叫做不可重复读

    • 范围多次读取,这样简单的添加行锁是不可能解决多次读取结果不一致的问题,因此需要在范围添加锁。我(个人理解,可能不正确)称范围多次读取结果不一致的问题,叫做幻读

    • 这也就可以解释,为什么唯一索引、或者主键索引,当要删除、修改一个不存在的行时,触发了间隙锁,锁定了某个范围,而记录存在时,只是触发了行锁(可以明确指定行)。从索引的概念来讲,可以明确锁定某个索引的,且该索引值不会出现重复的,就是不可重复读的问题,不能明确锁定某个索引、或者某个索引值是可重复的,那么就会触发间隙锁,也是幻读的问题。

  • 思考

    • 假设我们又新增了一个字段val,该字段也添加了索引(非唯一索引),如果执行下面的语句会触发间隙锁么?如果触发,是number索引添加间隙锁,还是val索引添加间隙锁,还是都添加呢?
select * from `gap_lock` where number = 3 and val = 3 for update;   
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容