两文说透MySQL里的各种锁(下篇)

在上篇中,我们介绍了MySQL中的全局锁和表锁。
今天,我们专注于介绍一下行锁,这个在日常开发和面试中常常困扰我们的问题。

两文说透MySQL里的各种锁(下篇)

  1. 行锁基础
    由于全局锁和表锁对v 5 } m增删改查的性能都会有较大影响,所以,我们自然会想到,
    只需要对有修改的行加锁就行了,这就是行锁。
    事务中,事务1更新了一行主键为1的数据行,那么,在这个事务释放锁之前,事务2是不能操作的。
    两文说透MySQL里的各种锁(下篇)

另外,有一个很多人容易混淆的概念,就是行锁什么时候释放?
搞清这个事情,需要了解什么叫作 两阶段锁。
什么是两阶段锁呢?举个例子你就明白了。
两文说透MySQL里的各种锁(下篇)

这里事务2执行后会是什么结果呢?
如果你明白了两阶段锁的含义,你就会知道,事务2的updat语句会阻塞,直到事务1提交以后才能继续执行。
所以,这里再次强调一下,两阶段锁的含义。
在 InnoDB 事务中,行锁是在需要的时候才加A V X f # 4 , m上的0 = 9 _ s i c,但并不是语句执行完了了就立刻释放, 而是要等到事务结束时才释放。
注意,除了update语句能加写锁外,另外,还有一种对select语句加写锁的方式,就是
当前读:Select …. for upda@ # gte
2.行锁进阶
2.1 什么是幻读
面试的时候,面试官经常会喜3 d ^ d t j Z欢问数据库的事务隔离级别
大家要能回答出四种隔离级别,四种隔离级别的含义。
再多问一点,3 H 3 Q | L| 4 $ j [ A 0问你什么是脏读,什么是幻读,哪个隔离级别会解决什么问题。
首先明确一下,什么是幻读?
同样是一个事务,在事务中^ 8 h T ? v k前后两次查询,c y a # ] n #出现了不同的结果。
不同之处在于,脏读是针对update,也就是同一行的数据出现了不一致。d % ]
注意,幻读出现的场景
事务的隔离级别为可重复读,且是当前读
幻读仅专指新插入的行,在范围查询中,后一次查询出现了新的数据行。
2.2 怎么解决幻读
这些如果你都能答上,面试官可能会继续追问,幻读是怎么产生的,又是怎么被解决的。
即使+ ~ c我们给所有update涉及的行都加上了行锁,还是无法解决新插入的记录,因为这些记录原本不存在,自然无法加上行锁+ ! a W 3 . Y +
那怎么办呢?为了解决这个问题,innodb只好引入新的锁,间隙锁(Gap Lock)。
间隙锁,锁的是两个值之间的空隙。
举个例子:
在四条记录,ID=0,10,20,30中,会产生如下的五个z t { S [ 5 E @ 3间隙范围
两文说透MySQL里的各种锁(下篇)

间隙锁就是对这五个间隙范围加锁,防止新的记录插入。
注意,行锁的冲H } % I n t *突是行与行之间的3 b U m冲突,是行锁与行锁之间的。与间隙锁冲突的是往“间隙中插入数据”这个操作,间隙锁本身不会产生冲突。
间隙锁和行锁合称@ o M r i j b E X为next-key lock。每个next-key lock是前开后闭的。间隙锁本身是前开后开的。
标准的事务隔离级别中,可重复读只解决脏读问= ! * G题,无法* ^ 5 K 2 v解决幻读问题。但是在innodb中,用next-key lock解决了幻J I v C Q D n u读的问题。
3.关于行锁的优化应用
3.1 两阶段锁的优化应用
上面的基础知识中,解释了什么是 两阶段锁。
那么,对我们业务开发中有什么$ z v借鉴意义呢?
既然我们知道了,行锁必须在整个事务完全提交后才会释放,那w { x 3 * E D么,如果我们的^ F % + W F o事物中需要锁住多行,就要把最可能造成锁冲突,或者是锁住最m * e % A G多行的语句尽可能地往后放。
举个例子,小A在线上购买了商家B的一个产品,这个购买的动作可以简化为3个操作:
1)小A的银行账户余额扣款x;
2)商家B的银行账户余额增加x;
3)添加一条交易记录;
这里,涉及到两个update操作,和一个insert操作。为了保证交易的原子性,将三个动作放在了一个事务中。
那怎么安排三个语句的先后顺序呢?如果u t q 8不仔细考虑,那么就可能是随意选个( $ & -123或者213的顺序了。
仔细想想呢?
显然,这里最容易造成冲突的是步骤2),可能同时有多个用户购买商家B的产品,然后需要给商 n T e B A X D家B的余额做update操作。
另外,步骤3)是insert操作,最不容易出现锁冲突。
所以,最好的步骤顺序是3)-> 1) -> 2),将最容易产生冲突的操作放在最后执行,那么会比2)->1) ->3)的顺序,大大提高并发度。
3.2 间隙锁的# 0 s . r问题与优化
间隙锁m * K { ! h L X ^的引入也带来了一些新的问题,比如:降低并发度,可能导致死锁。
因为间隙锁的引入,可能会导致同样的语句锁住了更大的范围。
那怎么办呢?
注意,间隙锁在可重复读级别下才是有效的。
所以,只要我们的业务不需要可重复读的保证,我们就可以把隔离级别设置为读提交(F [ I _ H u M 4 ,也是阿里云rds数据库的默认隔离级别),就没有间隙锁了。
然后,为了解决可能的数据和日志不一致的问题,需要把binlog格式设O 3 p G置为row。
读提交级别 + binlog的row格式,也是一般公司数据库的标准配置
现在,你知道原因了吧:)

参考:
丁奇《MySQ= - y r S b j ]L 实战45e ! ] K $ 3讲》

【MySQL系列相关】
1.聊一聊关于MySQL的count(*)
2.为什么MySQL分库分表后总存储G L大小变大了?
3.两文说透Mys , m 5SQE M d C A I #L里的各种锁(上篇)

原创:阿丸笔记(微信公众号:aone_note),欢迎分享,转载请保留出处。
扫描下方二维码可以关注哦~~~
两文说透MySQL里的各种锁(下篇)