共计 11027 个字符,预计需要花费 28 分钟才能阅读完成。
10 个行锁、死锁案例⭐️24 张加锁剖析图🚀彻底搞懂 Innodb 行锁加锁规定!
上篇文章 咱们形容原子性与隔离性的实现,其中形容读操作解决隔离性问题的计划时还遗留了一个问题:写操作是如何解决不同的隔离性问题?
本篇文章将会解决这个问题并形容 MySQL 中的锁、总结 Innodb 中行锁加锁规定、列举办锁、死锁案例剖析等
再浏览本篇文章前,至多要了解查问应用索引的流程、mvcc 等常识(不了解的同学能够依据专栏程序进行浏览)
MySQL 锁的分类
从锁的作用域上划分: 全局锁、表锁、页锁、行锁
- 全局锁:锁整个数据库实例,罕用数据备份,禁止全局写,只容许读
-
表锁:锁表,对表进行加锁
- 元数据锁:表构造批改时
- 表 X 锁:表独占锁
- 表 S 锁:表共享锁
- 页锁:位于表锁与行锁两头作用域的锁
- 行锁(innodb 特有):锁记录,其中蕴含独占锁(写锁,X 锁)和共享锁(读锁,S 锁)
从性能上划分:意向锁、插入意向锁、自增长锁、乐观锁、乐观锁 …
-
意向锁:表锁,示意无意向往表中加锁,获取行锁前会加意向锁,当须要加表锁时,要通过意向锁判断表中是否有行锁
- 独占意向锁 IX:意向往表中加 X 锁,兼容 IX、IS,不兼容 X、S(表级别)
- 共享意向锁 IS:意向往表中加 S 锁,兼容 IX、IS、S,不兼容 X(表级别)
- 插入意向锁:隐式锁,意向往表中插入记录
- 自增长锁:隐式锁,在并发场景下不确定插入数量会应用自增长锁加锁生成自增值,如果确定则应用互斥锁(间断模式)
- 乐观锁:乐观锁能够用加行锁实现
- 乐观锁:乐观锁能够用版本号判断实现
这些锁有些是 MySQL 提供的,有些是存储引擎提供的,比方 Innodb 反对的行锁粒度小,并发性能高,不易抵触
但在某些场景上行锁还是会发生冲突(阻塞),因而咱们须要深刻把握行锁的加锁规定能力在遇到这种场景时剖析出问题
Innodb 的行锁加锁规定
后面说到行锁分为独占锁 X 锁和共享锁 S 锁,行锁除了会应用这种模型外,还会应用到一些其余的模型
锁模型
record:记录锁,用于锁定某条记录 模型应用 S /X(也就是独占锁、共享锁)
gap:间隙锁,用于锁定某两条记录之间,禁止插入,用于避免幻读 模型应用 GAP
next key:临键锁,相当于记录锁 + 间隙锁 模型应用 X /S,GAP
在 lock mode 中罕用 X 或 S 代表独占 / 共享的 record,GAP 则不分 X 或 S
如果是独占的临键锁则是 X、GAP,共享的临键锁则是 S、GAP
(这个 lock mode 前面案例时会用到,示意以后 SQL 被哪种锁模型阻塞)
不同隔离级别下的加锁
加锁的目标就是为了可能满足事务隔离性从而达到数据的一致性
在 不同隔离级别、应用不同类型 SQL(增删改查)、走不同索引(主键和非主键、惟一和非惟一)、查问条件(等值查问、范畴查问)、MySQL 版本 等诸多因素都会导致加锁的细节发生变化
因而只须要大抵把握加锁规定,并联合产生阻塞的记录排查出产生阻塞的问题,再进行优化、解决即可(本文基于 5.7.X)
在 RU(Read Uncommitted)下,读不加锁,写应用 X record,因为写应用 X record 则不会产生脏写,会产生脏读、不可反复读、幻读,
在 RC(Read Committed)下,读应用 mvcc(每次读生成 read view),写应用 X record,不会产生脏写、脏读,但会有不可反复读和幻读
在 RR(Repeatable Read)下,读应用 mvcc(第一次读生成 read view),写应用 X next key,不会产生脏写、脏读、不可反复读和大部分幻读,极其场景的幻读会产生
在 S(Serializable)下,主动提交状况下读应用 mvcc,手动提交下读应用 S next key,写应用 X next key,不会产生脏写、脏读、不可反复读、幻读
在罕用的隔离级别 RC、RR 中,读都是应用 mvcc 机制(不加锁)来进步并发性能的
锁定读的加锁
在 S 串行化下,读会加 S 锁,那 select 如何加锁呢?
S 锁:select ... lock in share mode
X 锁:select ... for update
通常把加锁的 select 称为锁定读,而在一般的 update 和 delete 时,须要先进行读(找到记录)再操作,在这种状况下加锁规定也能够归为锁定读
update 与 delete 是写操作,必定是加 X 锁的
(以下锁定读和新增的加锁规定是总结,搭配案例查看,一开始看不懂不要紧~)
锁定读加锁规定
-
在 RC 及以下隔离级别,锁定读应用 record 锁;在 RR 及以上隔离级别,锁定读应用 next key 锁(间隙锁的范畴是前开后闭,案例详细描述)
(具体 S、X 锁则看 SQL,如果是
select ... lock in share mode
则是 S 锁,如果是select ... for update
、update ...
、delete ...
则是 X 锁) - 等值查问:如果找不到记录,该查问条件所在区间加 GAP 锁;如果找到记录,惟一索引临键锁进化为记录锁,非惟一索引须要扫描到第一条不满足条件的记录,最初临键锁进化为间隙锁(不在最初一条不满足条件的记录上加记录锁)
- 范畴查问:非惟一索引须要扫描到第一条不满足条件的记录(5.7 中惟一索引也会扫描第一条不满足条件的记录 8.0 修复,后文形容)
-
在查找的过程中,应用到什么索引就在那个索引上加锁,遍历到哪条记录就给哪条先加锁
(查找时走二级索引,如果要回表查聚簇索引,则还会在聚簇索引上加锁)
(批改时如果二级索引上也存在要批改的值,则还要去二级索引中查找加锁并批改)
- 在 RC 及以下隔离级别下,查找过程中如果记录不满足以后查问条件则会开释锁;在 RR 及以上无论是否满足查问条件,只有遍历过记录就会加锁,直到事务提交才开释(RR 及以上获取锁的工夫会更长)
新增的加锁
后面说到 update、delete 这种先查再写的操作能够看成加 X 锁的锁定读,而 select 的锁定读分为 S、X,还剩 insert 的规定没有阐明
新减少锁规定
新减少锁规定分为三种状况:失常状况、遇到反复抵触的状况、外键状况
新增时加的锁叫插入意向锁,它是隐式锁
当别的事务想要获取该记录的 X / S 锁时,查看该记录的事务 id 是不是沉闷事务,如果沉闷(事务未提交)则会帮新增记录的事务生成锁构造,此时插入意向锁变成显示锁(能够看成 X 锁)
失常状况下加锁:
- 个别状况下,插入应用隐式锁(插入意向锁),不生成锁构造
- 当插入意向锁(隐式锁)被其余事务生成锁构造时变为显示锁(X record)
反复抵触加锁:
- 当 insert 遇到反复主键抵触时,RC 及以下加 S record,RR 及以上加 S next key
-
当 insert 遇到反复惟一二级索引时,加 S next key
如果应用
ON DUPLICATE KEY update
那么 S 锁会换成 X 锁
外键加锁:个别不做物理外键,略 …
行锁案例剖析
搭建环境
先建设一张测试表,其中 id 为主键,以 s\_name 建设索引
CREATE TABLE `s` (`id` int(11) NOT NULL,
`s_name` varchar(255) DEFAULT NULL,
`s_age` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name_idx` (`s_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
再插入一些记录
INSERT INTO `s` (`id`, `s_name`, `s_age`) VALUES (1, 'juejin', '1');
INSERT INTO `s` (`id`, `s_name`, `s_age`) VALUES (10, 'nb', '10');
INSERT INTO `s` (`id`, `s_name`, `s_age`) VALUES (20, 'caicai 菜菜', '20');
INSERT INTO `s` (`id`, `s_name`, `s_age`) VALUES (25, 'ai', '25');
聚簇索引和 s\_name 索引的存储图像简化成如下:
后面说过 GAP 须要加在记录之间,如果是第一条记录或者最初一条记录要避免插入,该如何加 GAP 锁呢?
Infimum 和 Supremum 的呈现就可能解决这种问题,它们用于标识每页的最小值和最大值
留神:因为 RC、RR 是罕用的隔离级别,案例也是应用这两种隔离级别进行阐明
分析方法
能够通过零碎库查看行锁阻塞的相干信息
5.7 阻塞的锁、事务等信息在 information\_schema 库中的 innodb\_locks、innodb\_lock\_waits、innodb\_trx 等表
8.0 的相干信息则是在 performance\_schema 库中
lock 记录信息简介
lock\_id 锁 id 由事务 id、存储信息组成 会变动
lock\_trx\_id 事务 ID 42388 为先开启的事务(事务 ID 全局自增)
lock\_mode 阻塞的锁为 X 锁,还有其余模式:S、X、IS、IX、GAP、AUTO\_INC 等
lock\_type 锁类型为行锁 record,还有表锁:table
lock\_table 锁的表 ; lock\_index 锁的索引(二级索引)
lock\_space、page、rec 锁的表空间 id、页、堆号等存储信息
lock\_data 示意锁的数据,个别是行记录 ‘caicai 菜菜 ’,20 (s\_name,id)
还能够通过联表查问获取行锁阻塞的信息
SELECT
r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
r.trx_query waiting_query,
rl.lock_mode waiting_lock_mode,
rl.lock_type waiting_lock_type,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread,
b.trx_query blocking_query,
bl.lock_mode blocking_lock_mode,
bl.lock_type blocking_lock_type
FROM information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx b
ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.innodb_trx r
ON r.trx_id = w.requesting_trx_id
inner join information_schema.innodb_locks rl
on r.trx_id = rl.lock_trx_id
inner join information_schema.innodb_locks bl
on b.trx_id = bl.lock_trx_id;
又或者通过 innodb 的日志(show engine innodb status)查看阻塞信息 …
(后文剖析再说)
案例:RC、RR 下的加锁
T1 | T2 | |
---|---|---|
1 | begin;<br/>select * from s where id\>=10 and id<=20 for update; | |
2 | insert into s values (12,’caicaiJava’,12);<br/>(阻塞) | |
3 | commit; |
T1 事务在 10,20 之间会加 GAP 锁,因而 T2 新增时会被阻塞
设置为 RC 后不再阻塞,因为 RC 下不加 GAP 锁不避免插入
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@tx_isolation;
但如果是要获取记录锁则还是会被阻塞(批改 id 为 10 的记录 update s set s_name = '666' where id = 10
)
依据该案例能够阐明规定一:RC 及以下应用记录锁、RR 及以上应用临键锁
案例:等值查问
等值查问:匹配不到满足条件的记录
T1 | T2 | T3 | |
---|---|---|---|
1 | begin;<br/>select * from s where id=15 for update; | ||
2 | insert into s values (11,’caicaiJava11′,11);<br/>(阻塞) | ||
3 | insert into s values (19,’caicaiJava11′,19);<br/>(阻塞) | ||
4 | commit; |
通过阻塞记录能够看到 T2,T3 事务被主键索引上数据为 20 的临键锁(的 GAP)阻塞
等值查问如果匹配不到值会在该区间加 GAP 锁
图中向下黑箭头为 GAP 锁
例如 T1 等值查问 id=15,没有 id=15 的记录则会加锁在 15 这个区间加 GAP 锁
等值查问:匹配到满足条件的记录
T1 | T2 | T3 | |
---|---|---|---|
1 | begin;<br/>select * from s where id=20 for update; | ||
2 | insert into s values (15,’ 菜菜的后端私房菜 ’,15);<br/>(不阻塞) | ||
3 | update s set s\_name = ‘ 菜菜的后端私房菜 ’ where id = 20;<br/>(阻塞) | ||
4 | commit; |
因为惟一索引上雷同的记录只有一条,当等值查问匹配时,临键锁会进化成记录锁,因而 T2 不被阻塞 T3 被阻塞
图中为 T3 被数据为 20 上的 X 锁阻塞
惟一索引等值查问间隙锁进化为记录锁
(图中蓝色为记录锁)
非惟一索引等值查问
T1 | T2 | T3 | |
---|---|---|---|
1 | begin;<br/>select s\_name,id from s where s\_name=’caicai 菜菜 ’ for update; | ||
2 | insert into s values (15,’bilibili’,15);<br/>(阻塞) | ||
3 | insert into s values (18,’da’,18);<br/>(阻塞) | ||
4 | commit; |
为了确保 select s_name,id from s where s_name='caicai 菜菜' for update
应用 s\_name 索引,我将查问列换成 s\_name 上存在的列,防止回表(确保应用 s\_name)
- 先定位到
s_name='caicai 菜菜'
的记录,加锁:(ai,caicai 菜菜] - 因为不确定满足
s_name='caicai 菜菜'
的记录是否有反复,于是持续后查问,加锁:(caicai 菜菜,juejin] - 因为 juejin 不满足查问条件,于是进化为间隙锁,加锁:(caicai 菜菜,juejin)
最终加锁范畴 = (ai,caicai 菜菜] + (caicai 菜菜,juejin) = (ai,juejin)
(留神:我这里的加锁范畴是简化的,没有带上主键信息;残缺信息如下图 lock\_data 中的 juejin,1)
而后再来剖析 T2,T3 的插入语句,首先它们须要在聚簇索引和 name\_idx 索引上新增数据,因为聚簇索引未加锁,因而不影响插入
然而 name\_idx 索引上存在锁,T2 事务 bilibili 会插入到 ai 和 caicai 菜菜记录之间,T3 事务会插入到 caicai 菜菜和 juejin 这两条记录间,因而被 GAP 锁阻塞
通过阻塞记录也能够看出 T2,T3 均被临键锁阻塞
至此等值查问的案例剖析结束,小结如下:
- 等值查问找不到记录:该区间加 GAP 锁
-
等值查问找到记录
- 惟一索引:临键锁会进化为记录锁
- 非惟一索引:始终扫描到第一条不满足条件的记录并将临键锁进化为间隙锁
案例:范畴查问
在下面等值查问 + 非惟一索引的场景下,因为无奈判断该值数量,因而会始终扫描,能够把这种场景了解成范畴查问
T1 | T2 | |
---|---|---|
1 | begin;<br/>select * from s where id\>=10 and id<=20 for update; | |
2 | insert into s values (21,’caicaiJava’,21);<br/>(阻塞) | |
3 | commit; |
依照失常思路来说,我的查问条件在 10-20,那么就不能往这个范畴外再加锁了
然而新增该范畴外的记录是会阻塞的(我明明查问条件在 10\~20,后果超过 20 你也给我加锁是吧?)
咱们来剖析下 T1 加锁过程:id>=10 and id<=20
- 定位第一条记录 (id=10),按情理加间隙锁(前开后闭) 应该是(1,10],然而有等值查问的优化,间隙锁进化为记录锁,因而只对 10 加锁 [10]
- 持续向后范畴扫描,定位到记录(id=20),加锁范畴(10,20]
- 依照失常思路主键是惟一的,我曾经找到一条 20 了,那我应该退出才对呀,然而它还是会持续扫描,直到第一条不满足查问条件的值 (id=25) 并将临键锁锁进化成间隙锁,也就是不在 25 加记录锁,因而加锁范畴(20,25)
最终加锁范畴 [10] + (10,20] + (20,25) = [10,25),因而插入主键为 21 时会被阻塞
思考:依照失常的思路,当在非惟一索引上时,这么扫描没问题,因为不晓得满足后果的 20 有多少条,只能往后扫描找到第一条不满足条件的记录;而在惟一索引上找到最初一个满足条件的记录 20 后,还持续往后加锁是不是有点奇怪呢?
我在 8.0 的版本中重现这个操作,插入 id=21 不再被阻塞,应该是在惟一索引上扫描到最终满足条件的记录(id=20)就完结,加锁范畴如下图(在 5.7 中这应该算 bug)
范畴查问时无论是否惟一索引都会扫描到第一条不满足条件的记录,而后临键锁进化为间隙锁(8.0 修复惟一索引范畴查问时的 bug)
案例:查找过程中怎么加锁
T1 | T2 | |
---|---|---|
1 | begin;<br/>update s set s\_name = ‘caicai 菜菜 ’ where id = 20; | |
2 | select s\_name,id from s where s\_name like ‘cai%’ for update; <br/>(阻塞) | |
3 | commit; |
T1 事务在批改时先应用聚簇索引定位到 id=20 的记录,批改后通过主键 id=20 找到二级索引上的记录进行批改,因而聚簇索引、二级索引上都会获取锁
T2 事务锁定读二级索引时,因为查问条件满足二级索引的值,因而不须要回表,但因为 T1 事务锁住二级索引上的记录,因而产生阻塞
在该案例中阐明:加锁时应用什么索引就要在那个索引上加锁,遍历到哪些记录就要在哪些记录上加锁
delete:与主键相干的二级索引必定也要删除,因而二级索引上对应主键值的记录也会被加锁
update:如果在二级索引上批改,那么肯定回去聚簇索引上批改,因而聚簇索引也会被加锁;如果在聚簇索引上批改,二级索引可能会须要被加锁(如上案例,如果批改的是 s\_age 那么二级索引就不须要加锁)
select:应用什么索引就在什么索引上加锁,比方应用聚簇索引就要在聚簇索引上加锁,应用二级索引就在二级索引上加锁(如果要回表也要在聚簇索引上加锁)
案例:RC、RR 什么时候开释锁
RC 及以下,RR 及以上在获取完锁后,开释锁的机会也不同
RR 下
T1 | T2 | T3 | |
---|---|---|---|
1 | begin;<br/>update s force index (name\_idx) set s\_age = 20 where s\_name \> ‘c’ and s\_age \> 18; | ||
2 | select * from s where id = 1 for update;<br/>(阻塞) | insert into s values (33,’zz’,33);<br/>(阻塞) | |
3 | commit; |
T3 插入的记录满足 s_name > 'c' and s_age > 18
的记录被阻塞情有可原
那为啥 T2 id= 1 不满足 s_name > 'c' and s_age > 18
也被阻塞了呢?
T1 事务是一条批改语句,我应用 force index 让它强制应用 name\_idx 索引,查问条件为 s_name > 'c' and s_age > 18
因为 name\_idx 上不存在 s\_age,须要判断 s\_age 就要去聚簇索引,因而聚簇索引上也会被加锁
T1 在 name\_idx 上,依据查问条件 s\_name > ‘c’ 进行加锁
- 定位第一条 s\_name 大于 c 的记录,加锁(ai,caicai 菜菜]
- 依据主键值 id=20 去聚簇索引中找到该记录,加锁[20,20]
- 查看是否满足 s\_age>18 的条件,如果满足则进行批改(不满足不会开释锁)
- 持续循环,回到 name\_idx 上寻找下一条记录(直到不满足查问条件的记录或遍历完记录则退出)
依据 1 - 3 的步骤,会在索引上这样加锁
最终加锁状态:(name\_id 中的 +∞则指的是 supremum)
其中只有 id=20 的记录满足 s_name > 'c' and s_age > 18
,即便这些记录不满足条件也不会开释锁
因而 T2 要获取聚簇索引 id= 1 的记录时被阻塞,而 T3 则是被 supremum 阻塞
在 RR 下应用的索引遍历到哪就把锁加到哪,即便不满足查问条件也不会开释锁,直到事务提交才开释
RC
设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@tx_isolation;
T1 | T2 | T3 | |
---|---|---|---|
1 | begin;<br/>update s force index (name\_idx) set s\_age = 20 where s\_name \> ‘c’ and s\_age \> 18; | ||
2 | select * from s where id = 1 for update;<br/>(不阻塞) | select * from s where id = 20 for update;<br/>(阻塞) | |
3 | commit; |
遍历流程与 RR 状况类似,不同的是 RC 只加记录锁,并且不满足条件的记录会立刻开释锁,因而 T2 不被阻塞,满足条件的 T3 被阻塞
加锁如下图
遍历到哪条记录就先加锁,然而 RC 对于不满足查问条件的记录会开释锁
死锁案例剖析
死锁案例剖析的是 insert 加的锁,配合下面新减少锁规定查看
案例:新增死锁
先将 name\_idx 改为惟一索引
T1 | T2 | |
---|---|---|
1 | begin;<br/>insert into s values (5,’bilibili’,5); | |
2 | insert into s values (7,’bilibili’,7); <br/>(阻塞) | |
3 | insert into s values (6,’balibali’,6); | |
4 | 死锁 回滚 |
T1 插入 bilibili,T2 也插入 bilibili,依照情理应该报错惟一键反复呀,T2 怎么阻塞了呢?
T1 后续再插入 balibali 居然产生死锁了!啥状况呀?同学们能够先依据后面说到的 insert 加锁规定,大胆猜想喔 \~
查看最近的死锁日志
须要留神的是 innodb lock 表中锁相干信息记录只有正在产生时才存在,像这种产生死锁,回滚事务后是看不到的,因而咱们来看看死锁日志
show engine innodb status 查看 innodb 状态,其中有一段最近检测到的死锁 latest detected deadlock
红色框示意事务和持有 / 期待的锁
绿色框示意锁的信息(都是同一把 X 锁)
如果日志还是看不太懂的话,来看看上面这段剖析吧(次要说 name\_idx 索引上的流程哈)
1、T1 插入 bilibili(隐式锁)
2、T2 插入 bilibili 发生冲突,T2 帮 T1 生成锁构造(隐式锁转化为显示锁,T1 取得 X record),T2 要加 S 临键锁,先获取 GAP 锁(胜利),再获取 S 锁(被 T1 的 X record 阻塞)
T1 事务 id 为 42861,T2 事务 id 为 42867:依据锁信息能够看到 T2 想加的 S 锁被 T1 的 X 锁阻塞
3、T1 插入 balibali,插入意向锁被 T2 的 GAP 锁阻塞(死锁成环:T1 期待 T2 的 GAP,T2 期待 T1 的 X)
图中蓝色与 T1 无关,彩色与 T2 无关
T1:持有[bilibili,bilibili] X 锁,要插入 balabala 被 T2 的间隙锁(ai,bilibili)阻塞
T2:持有间隙锁(ai,bilibili),要插入[bilibili,bilibili]S 锁被 T1 的[bilibili,bilibili]X 锁阻塞
那么如何解决死锁呢?
先来看看死锁产生的四个条件:互斥、占有资源不放、占有资源持续申请资源、期待资源成环
MySQL 通过回滚事务的形式解决死锁,也就是解决占有资源不放
但 MySQL 死锁检测是十分消耗 CPU 的,为了防止死锁检测,咱们应该在业务层面避免死锁产生
首先互斥、占有资源不放两个条件是无奈毁坏的,因为加锁由 MySQL 来实现
而毁坏占有资源持续申请资源的代价可能会很大,比方:让业务层加锁解决
性价比最高的应该是毁坏期待资源成环,当产生死锁时,通过剖析日志、加锁规定,调整业务代码获取资源的程序防止产生死锁
案例:雷同的新增产生死锁
T1 | T2 | T3 | |
---|---|---|---|
1 | insert into s values (15,’bili’,15); | ||
2 | insert into s values (15,’bili’,15);<br/>(阻塞) | insert into s values (15,’bili’,15);<br/>(阻塞) | |
3 | rollback; | ||
4 | 死锁 |
T1、T2、T3 新增雷同的记录
T1 新增后,T2、T3 会帮 T1 生成锁构造 X 锁从而被阻塞
当 T1 回滚时,T2,T3 居然产生死锁?
剖析流程
- T1 插入 加隐式锁
- T2 插入雷同惟一记录,帮 T1 生成 X 锁,本人获取 S next key,先获取 gap(胜利),再获取 S record(此时被 T1 的 X 锁阻塞);T3 与 T2 类似,获取到 gap 再获取 S record 时被 T1 的 X 阻塞
- T1 回滚,T2、T3 获取 S record 胜利,此时它们都还要获取 X record(插入意向锁转化为显示锁 X)导致死锁成环(T2 要加 X 锁被 T3 的 GAP 阻塞,T3 要加 X 锁被 T2 的 GAP 阻塞)
图中 T2、T3 都对 bili 加 S next key 锁(橙色记录和后面的彩色间隙),当它们都想加插入动向 X 锁(蓝色记录),同时也被各自的 GAP 锁阻塞
查看死锁日志
总结
本篇文章通过大量案例、图例剖析不同状况下的行锁加锁规定
update、delete 先查再改,能够看成锁定读,insert 则是有独自一套加锁规定
锁定读加锁规定
在 RC 及以下隔离级别,锁定读应用 record 锁;在 RR 及以上隔离级别,锁定读应用 next key 锁
等值查问:如果找不到记录,该查问条件所在区间加 GAP 锁;如果找到记录,惟一索引临键锁进化为记录锁,非惟一索引须要扫描到第一条不满足条件的记录,最初临键锁进化为间隙锁(不在最初一条不满足条件的记录上加记录锁)
范畴查问:非惟一索引须要扫描到第一条不满足条件的记录(5.7 中惟一索引也会扫描第一条不满足条件的记录 8.0 修复,后文形容)
在查找的过程中,应用到什么索引就在那个索引上加锁,遍历到哪条记录就给哪条先加锁
在 RC 及以下隔离级别下,查找过程中如果记录不满足以后查问条件则会开释锁;在 RR 及以上无论是否满足查问条件,只有遍历过记录就会加锁,直到事务提交才开释
insert 加锁规定
失常状况下加锁:
- 个别状况下,插入应用隐式锁(插入意向锁),不生成锁构造
- 当插入意向锁(隐式锁)被其余事务生成锁构造时变为显示锁(X record)
反复抵触加锁:
- 当 insert 遇到反复主键抵触时,RC 及以下加 S record,RR 及以上加 S next key
- 当 insert 遇到反复惟一二级索引时,加 S next key
如果应用 ON DUPLICATE KEY update
那么 S 锁会换成 X 锁
外键加锁:个别不做物理外键,略 …
最初(不要白嫖,一键三连求求拉 \~)
本篇文章被支出专栏 MySQL 进阶之路,感兴趣的同学能够继续关注喔
本篇文章笔记以及案例被支出 gitee-StudyJava、github-StudyJava 感兴趣的同学能够 stat 下继续关注喔 \~
有什么问题能够在评论区交换,如果感觉菜菜写的不错,能够点赞、关注、珍藏反对一下 \~
关注菜菜,分享更多干货,公众号:菜菜的后端私房菜
本文由博客一文多发平台 OpenWrite 公布!