Innodb-行锁

基本概念

脏读是指读到别的事务未提交的修改。
不可重复度读与幻读的区别,不可重复读的重点在于update和delete(读不到已提交的修改),幻读的重点在于insert。

隔离级别

快照读与当前读

在多版本并发控制中,读操作分成两类:

  • 快照读(snapshot read):快照读,读取的是记录的可见版本,可能是历史版本,不用加锁。
  • 当前读(current read):读取读,读取的是最新版本,当前读返回的记录,都会加锁,保证其他事务不会并发地修改这条记录。
    简单的select操作属于快照读,如下所示,具体的原理参看MVCC。
select * from table where xxx;

特殊读、插入、更新、删除操作,属于当前读,需要加锁,如下所示。

select * from table where xxx lock in share mode;
select * from table where xxx for update;
insert into table values(xxx);
update table set xxx where xxx;
delete from table where xxx;

RR级别下快照读与当前读不一致的问题

http://mysql.taobao.org/monthly/2017/06/07/

RC级别复制导致数据不一致问题

参看下文的例子,因为复制commit的顺序导致当前读数据不一致了。
RR就不会出现这个问题,因为RR会加上Gap锁

https://www.cnblogs.com/fanguangdexiaoyuer/p/11323248.html

锁类型

在 Innodb 内部用一个 unsiged long 类型数据表示锁的类型, 如图所示,最低的 4 个 bit 表示 lock_mode, 5-8 bit 表示 lock_type, 剩下的高位 bit 表示行锁的类型。


image.png

因此Innodb中的锁是上面三者的组合,比如LOCK_S|LOCK_REC|LOCK_ORDINARY表示S类型的间隙行锁。状态码= 2048+512+32
对于行锁来说,lock_mode只使用到了LOCK_S、LOCK_X。

锁冲突矩阵

对于lock_mode来说错冲突矩阵为
对于行锁类型的锁冲突矩阵为

组合起来就是

image.png

整个冲突矩阵可以理解为任何行锁类型的LOCK_S都与LOCK_S兼容,LOCK_S与LOCK_X、LOCK_X与LOCK_X的兼容要就要考虑行锁类型了,比如GAP与GAP兼容,LOCK_X|GAP就与LOCK_X|GAP兼容。
行锁类型要特别注意GAP锁与insert intention lock,GAP锁会阻塞insert intention lock。事实上,GAP锁的存在只是为了阻塞insert intention lock,RR级别解决当前读的幻读问题就是通过他们实现的。insert intention lock不阻塞任何锁。

加锁的具体分析

影响加锁的因素

  • 当前事务的隔离级别
  • SQL是一致性非锁定读(consistent nonlocking read)还是DML(INSERT/UPDATE/DELETE)或锁定读(locking read)
  • SQL执行时是否使用了索引,所使用索引的类型(主键索引,辅助索引、唯一索引)

加锁的顺序

加锁的因素里讲到了使用的索引,给同一条记录不同索引加锁的顺序也跟使用哪个索引定位有关。
比如通过主键索引定位的,则先给主键索引加锁,再给辅助索引、唯一索引加锁。
如果更新的内容不涉及辅助索引或唯一索引,则不需要给该索引加锁。如下图。


image.png

当然Delete需要给全部索引加锁。

MySQL二阶段锁

传统RDBMS加锁的一个原则,就是2PL (二阶段锁):Two-Phase Locking。相对而言,2PL比较容易理解,说的是锁操作分为两个阶段:加锁阶段与解锁阶段,并且保证加锁阶段与解锁阶段不相交。下面,仍旧以MySQL为例,来简单看看2PL在MySQL中的实现。

image.png

释放锁

大多数情况下事务锁都是在事务提交或回滚时释放(二阶段锁思想),但有三种意外,两种是主动释放、一种是被动释放:

  • AUTO-INC锁在SQL结束时直接释放(innobase_commit --> lock_unlock_table_autoinc);
  • 在RC隔离级别下执行DML语句时,从引擎层返回到Server层的记录,如果不满足where条件,则需要立刻unlock掉(ha_innobase::unlock_row)。
    示例如下图,下面第二张图中因为val条件不满足where查询,最终释放。


    image.png

    image.png
  • 锁等待情况下,如果等待的数据记录消失(比如回滚),本锁可能就消失了。
    如下图,RC级别的锁等待。


    image.png

    RR级别这种情况下的等待锁不会消失,会变为下一条记录的GAP锁,防止幻读。细节参看锁分裂和继承。


    image.png

锁分裂与继承

很多复杂的锁分析都依赖这个东西,这个要掌握理解,就可以应对多种情况下的锁变化了。


image.png

image.png

非冲突情况下更新的加锁实现

等值查询

select * from table where col=xxx lock in share mode; //把下图中X锁更换为S锁
select * from table where col=xxx for update;
update table set xxx where col=xxx;
delete from table where col=xxx;

加锁情况


image.png

范围查询

范围查找因为B树定位的问题,细节上有些变化。

加锁情况

Insert加锁实现

加锁涉及的几个阶段

1.唯一性约束检查,对于主键或唯一索引判断是否已存在唯一键的记录。
    1)如果存在,判断该记录上是否有活跃事务,
        (1)有活跃事务,给该事务加上冲突记录的锁,然后给自身事务加上该冲突记录的等待S锁,进入锁等待阶段。
        (2)如果没有活跃事务,则给自身事务加上该记录的S锁。
    2)如果不存在,则不加锁。
2.判断是否是DB_DUPLICATE_KEY(row_ins_dupl_error_with_rec),会返回给客户端报错,但不会释放1阶段中加的锁(对于唯一索引,还要回滚主键上已经插入成功的记录(RR下会产生GAP锁))。
3.对插入位置的下一条记录进行锁兼容检查,监测插入位置下一条记录上是否与与插入意向锁冲突的记录。
    1)如果有,给自身事务加上该记录的等待锁。
    2)如果没有,不加锁插入数据。

针对1->1)->(1)中锁等待的后续可能性:
   - 活跃事务提交,获取到等待的S锁,然后在第2阶段报错。
   - 活跃事务回滚,因为当前等待锁的目标记录消失了,发生锁分裂和继承,该等待锁成为目标记录下一条记录的S锁(主键和唯一键测试都是GAP锁)。然后进行2、3阶段检测,如果最终插入成功,还会发生锁分裂和继承,即把下一条记录的S锁,继承到自身上。

row_ins_clust_index_entry
----row_ins_clust_index_entry_low
--------btr_pcur_open //调用btr_cur_search_to_nth_level 查询索引树,将cursor移动到待插入记录相应的位置,Insert总使用PAGE_CUR_LE,所以会定位到待插入记录的前一条记录
--------如果判断存在唯一冲突,进入下面函数加锁
--------if (!index->allow_duplicates // 是否是唯一索引
        && n_uniq //唯一索引的字段数量
        && (cursor->up_match >= n_uniq || cursor->low_match >= n_uniq)) { //是否存在唯一索引冲突
------------row_ins_duplicate_error_in_clust //1,2阶段都发生在这个函数里,详细参看//www.greatytc.com/writer#/notebooks/48095140/notes/80218523
--------}
--------if (!index->allow_duplicates && row_ins_must_modify_rec(cursor)) { //判断能否原地更新,参看row_ins_must_modify_rec注释,主键原地更新的前提, 唯一字段一致满足逻辑原地更新的条件(但是能不能做到物理原地更新还要考虑存储空间是否满足,这个判断是发生在update中)
------------能走到这里,且没发生唯一冲突的,说明已存在的唯一键冲突的记录都是delete-mark的
------------btr_cur_optimistic_update or btr_cur_pessimistic_update
--------}else{
------------btr_cur_optimistic_insert or btr_cur_pessimistic_insert
----------------btr_cur_ins_lock_and_undo
--------------------lock_rec_insert_check_and_lock
------------------------判断是否与插入意向锁冲突
------------------------lock_rec_other_has_conflicting//3阶段
----------------------------因为上文定位到的是待插入记录的前一条记录,所以这里比较插入意向锁,要获取的是定位记录下一条记录的锁
----------------------------next_rec = page_rec_get_next_const(rec);
----------------------------heap_no = page_rec_get_heap_no(next_rec);
-------}
//www.greatytc.com/p/8607f106525c

冲突情况下的加锁实现

加锁类型

主键冲突,冲突记录已提交

image.png

主键冲突,冲突记录未提交

image.png

image.png

唯一索引冲突,冲突记录已提交

image.png

唯一索引冲突,冲突记录未提交

image.png

image.png

主键冲突,冲突记录回滚

下面发生死锁的原因是因为记录回滚,该等待锁继承成为目标记录下一条记录的S锁(Gap),然后与第三阶段的插入意向锁冲突。
一个思考:为什么唯一键冲突需要加s锁,如果这个s锁的目标记录回滚后,需要转换为Gap锁。
因为这个记录没了,所以无法在这个记录上加锁,如果有多个等待的s锁,不转成gap,一起往下走,包括latch这些东西都没有办法再阻止他们插入了。

RC
image.png
RR
image.png
插入成功后发生锁分裂
image.png

image.png
插入记录成功后发生锁分裂

RR级别唯一索引,发生回滚然后死锁,最终情况见下面第二张图。
有个思考,其实插入记录成功后,后面的S|GAP锁能释放吗?其实理论上可以释放,但是违反了二阶段锁协议。


image.png

image.png

存在Delete Mark记录情况下,主键与唯一键加锁的区别

这里面有几个前提:

  • 主键存在
  • 二级索引的更新总是先delete再insert
  • 相同uniq字段的delete-mark的记录总是非delete-mark记录的前方(左方),这是MySQL B树Insert操作定位search mode采用PAGE_CUR_LE决定的。
  • 因此二级唯一索引如果没有非delete-mark的重复记录,就需要把锁加到插入位置,具体看代码。


    image.png
RC主键
image.png
RR主键
image.png
RC唯一索引
image.png
RR唯一索引
image.png

当前读优化-半一致读

半一致读前提:

  • 对Update生效,对Delete不生效。
  • RC隔离界别或开启了innodb_locks_unsafe_for_binlog
  • 发生了锁等待
  • 必须是全表扫描 && 该索引是二级索引
    案例:
https://mp.weixin.qq.com/s?__biz=MzU0MTczNzA1OA==&mid=2247483804&idx=1&sn=c4360f68444bdc91ff46f662c3f27f9e&chksm=fb242891cc53a187231c797be8777633ccf2e951cb99e63298b944cba5ff6a84000f188208bb&mpshare=1&scene=1&srcid=0331LzGltSmGsIdUujfl5KN6%23rd

疑问:update全表扫遇到未提交的insert,半一致读生效吗?生效。


image.png

RR级别就需要等待。


image.png

https://mp.weixin.qq.com/s?__biz=MzU2NzgwMTg0MA==&mid=2247484177&idx=1&sn=03916542bbfd8262811142c1db39a5b7&chksm=fc96e18ecbe168983b4c1fd3807948905d38429065dd3d7c112e0bbad329bcc3710c38d1ecc9&scene=21%23wechat_redirect
http://hedengcheng.com/?p=771
https://www.itcodemonkey.com/article/13003.html
http://mysql.taobao.org/monthly/2017/12/02/
http://mysql.taobao.org//monthly/2016/01/01/
https://github.com/Yhzhtk/note/issues/42
https://zhuanlan.zhihu.com/p/52098868
https://zhuanlan.zhihu.com/p/52100378
https://zhuanlan.zhihu.com/p/52234835
https://mp.weixin.qq.com/s?__biz=MzU0MTczNzA1OA==&mid=2247483804&idx=1&sn=c4360f68444bdc91ff46f662c3f27f9e&chksm=fb242891cc53a187231c797be8777633ccf2e951cb99e63298b944cba5ff6a84000f188208bb&mpshare=1&scene=1&srcid=0331LzGltSmGsIdUujfl5KN6%23rd
https://blog.51cto.com/yanzongshuai/2106100
https://www.aneasystone.com/archives/2018/06/insert-locks-via-mysql-source-code.html
https://mp.weixin.qq.com/s/RleocRPvK67aTJqbDXeICw

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

推荐阅读更多精彩内容

  • 转自:https://www.cnblogs.com/AaronCui/p/10490663.html 意向锁:h...
    托盘毛毛阅读 204评论 0 0
  • InnoDB的锁机制浅析 1. 前言 数据事务设计遵循ACID的原则。 MySQL数据库提供了四种默认的隔离级别,...
    Aaron_Cui阅读 202评论 0 0
  • 1. mysql锁知多少 我们进行insert,update,delete,select会加锁吗,如果加锁,加锁步...
    liwsh阅读 4,976评论 0 4
  • 数据事务设计遵循ACID的原则: 原子性(Atomicity)、一致性(Consistency)、隔离性(Isol...
    AnyL8023阅读 497评论 0 0
  • 为什么开发人员必须要了解数据库锁? 1.锁? 1.1何为锁 锁在现实中的意义为:封闭的器物,以钥匙或暗码开启。在计...
    必成_d2f5阅读 157评论 0 1