本文整理自网络,侵删。
目录
- 1. Lock 与 Latch
- 2. Repeatable Read
- 3. Insert加锁流程
- 3.1 lock mode
- 3.2 加锁流程
- 3.3 隐式锁
- 4. Select 加锁流程
本文前提:
代码MySQL 8.0.13
只整理Repeatable Read
当前读。Read Committed
简单很多,另外快照读是基于MVCC
不用加锁,所以不在本文讨论范畴。
1. Lock 与 Latch
InnoDB
中的lock
是事务中对访问/修改的record
加的锁,它一般是在事务提交或回滚时释放。latch是在BTree上定位record
的时候对Btree pages加的锁,它一般是在对page中对应record
加上lock并且完成访问/修改后就释放,latch的锁区间比lock小很多。在具体的实现中,一个大的transaction
会被拆成若干小的mini transaction(mtr
),如下图所示:有一个transaction
,依次做了insert
,select…for update
及update
操作,这3个操作分别对应3个mtr,每个mtr完成:
- 在btree查找目标
record
,加相关page latch
; - 加目标
record lock
,修改对应record
- 释放
page latch
为什么要这么做呢?是为了并发,事务中的每一个操作,在步骤二完成之后,相应的record
已经加上了lock保护起来,确保其他并发事务无法修改,所以这时候没必要还占着record
所在的page latch
,否则其他事务 访问/修改 相同page
的不同record
时,这本来是可以并行做的事情,在这里会被page latch
会被卡住。
lock是存在lock_sys->rec_hash
中,每个record lock
在rec_hash
中通过<space_id
, page_no
, heap_no>
来标识
latch
是存在bufferpool
对应page
的block
中,对应block->lock
本文只关注lock相关的东西,latch后面单独搞一篇整理
2. Repeatable Read
具体每个隔离级别就不展开说了,这里主要说下RR,从名字上也能看出来,RR支持可重复度,也就是在一个事务中,多次执行相同的SELECT…FOR UPDATE
应该看到相同的结果集(除本事务修改外),这个就要求SELECT的区间里不能有其他事务插入新的record,所以SELECT除了对满足条件的record加lock之外,对相应区间也要加lock来保护起来。在InnoDB的实现中,并没有一个一下锁住某个指定区间的锁,而是把一个大的区间锁拆分放在区间中已有的多个record上来完成。所以引入了Gap lock和Next-key lock的概念,它们加再一个具体的record上
Gap lock
保护这个record与其前一个record之间的开区间Next-key lock
保护包含这个record与其前一个record之间的左开右闭区间
它们都是为了保护这个区间不能被别的事务插入新的record,实现RR。
接下来从源码实现上来分别看下Insert和Select是如何加lock的,结合着看也就知道InnoDB的RR是如何实现的了。Insert的加锁分布在Insert操作的过程中,遍布在多个相关的函数里,Select的加锁则比较集中,就在row_search_mvcc
里。
3. Insert加锁流程
3.1 lock mode
lock的mode主要有Share(S)和Exclusive(X)【代码中对应LOCK_S和LOCK_X】
lock的gap mode主要有Record lock, Gap lock, Next-key lock【代码中对应LOCK_REC_NOT_GAP, LOCK_GAP, LOCK_ORDINARY】
在具体使用中将 mode|gap_mode 之后就是一个lock的实际类型,Record lock是作用在单个record上的记录锁,Gap lock/Next-key lock
虽然也是加在某个具体record上,但作用是为了确保record前面的gap不要有其他并发事务插入,这个具体是怎么实现呢,InnoDB引入了一个插入意向锁,他的实际类型是
(LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION)
与Gap lock/Next-key lock
互斥,如果要插入前检测到插入位置的next record上有lock,则会尝试对这个next record加一个插入意向锁,代表本事务打算给这个gap里插一个新record,看行不行?如果已经有别的事务给这里上了Gap/Next-key lock
,代表它想保护这里,所以当前插入意向锁需要等待相关事务提交才行。这个检测只是单向的,即插入意向锁需等待Gap/Next-key lock
释放,而任何锁不用等待插入意向锁释放,否则严重影响这个gap中不冲突的Insert操作并发。
具体的锁冲突检测在lock_rec_has_to_wait函数中,大体原则就是:判断两个lock兼容还是不兼容,首先先做mode的冲突检测
如果不冲突,则代表锁兼容,无需等待,如果冲突,则接着做gap mode的冲突例外检测,整理如下:
如果gap mode不冲突,则作为例外情况可以认为锁兼容,无需等待。可以看到:
- 插入意向锁需要等待
Gap lock
及Next-key lock
- 任何锁不用等待插入意向锁
Gap lock
无需等待任何锁Next-key lock
需要等待其他Next-key lock及Record Lock
,反之亦然
了解了这些锁兼容原则,接下来就可以看在实际Insert流程中是如何使用它们的。
3.2 加锁流程
Insert
的顺序是先插入主键索引,再依次插入二级索引。以下是从代码中整理出来的流程,插入某个entry
的操作,
【对于主键索引】:
(1)先在查找Btree,加相关page latch
,定位到entry对应插入位置的record (<= entry)
(2)如果要插入的entry已经存在,即entry = record
,此时接着判断:
- 如果是
INSERT ON DUPLICATE KEY UPDATE
,则对record
加X Next-key lock
- 如果是普通
INSERT
,则对record
加S Next-key lock
之后接着判断record是否是deleted mark:
- 如果不是delete mark,说明的确有duplicate,返回
DB_DUPLICATE_KEY
到上层,然后上层通过看是INSERT ON DUPLICATE KEY UPDATE
还是普通INSERT来决定是转成update操作继续还是给用户报错duplicate - 如果是deleted mark,则说明实际没有
duplicate record
,接着往下走
(3)判断record的下一个record上当前有没有锁,如果有的话,则给其加插入意向锁,确保要插入entry的区间没有其他Gap lock/Next-key lock
保护
(4)插入entry
(5)释放page latch
,此时依旧占有lock
【对于二级索引】
(1)先在查找Btree,加相关page latch,定位到entry对应插入位置的record (<= entry)
(2)如果要插入的entry已经存在,即entry = record
,并且当前index是unique:
- 如果是
INSERT ON DUPLICATE KEY UPDATE
,则对record
加X Next-key lock
- 如果是普通INSERT,则对
record2
加S Next-key lock
判断record与entry是否相等:
相关阅读 >>
《阿里巴巴java开发手册》里面写超过三张表禁止join,这是为什么?
更多相关阅读请进入《mysql》频道 >>

数据库系统概念 第6版
机械工业出版社
本书主要讲述了数据模型、基于对象的数据库和XML、数据存储和查询、事务管理、体系结构等方面的内容。
转载请注明出处:木庄网络博客 » MySQL InnoDB 事务锁源码分析
相关推荐
评论
管理员已关闭评论功能...