前语
我们好,我是田螺。
金三银四很快就要来啦,预备了数据库锁的12连问,相信我们看完肯定会有协助的。
- 大众号:捡田螺的小男孩 (有田螺精心预备的面试PDF)
- github地址,感谢每颗star:github
1. 为什么需求加锁
在日常生活中,假如你心情不好想静静,不想被比别人打扰,你就能够把自己关进房间里,而且反锁。这便是生活中的加锁。
同理,关于MySQL数据库来说的话,一般的对象都是一个业务一个业务来说的。所以,假如一个业务内,一个SQL正在更新某条记载,咱们肯定不想它被其他业务影响到嘛?因而,数据库规划大叔,给该行数据加上锁(行锁)。
专业一点的说法: 假如有多个并发恳求存取数据,在数据就可能会产生多个业务一起操作同一行数据。假如并发操作不加操控,不加锁的话,就可能写入了不正确的数据,或许导致读取了不正确的数据,破坏了数据的一致性。因而需求考虑加锁。
2. InnoDB有哪些锁?
2.1 同享/排他锁
InnoDB呢完结了两种标准的行级锁:同享锁(简称S锁)、排他锁(简称X锁)。
- 同享锁:简称为S锁,在业务要读取一条记载时,需求先获取该记载的S锁。
- 排他锁:简称X锁,在业务需求改动一条记载时,需求先获取该记载的X锁。
假如业务T1
持有行R的S
锁,那么另一个业务T2
恳求拜访这条记载时,会做如下处理:
- T2 恳求
S
锁当即被答应,结果T1和T2
都持有R行的S
锁 - T2 恳求
X
锁不能被当即答应,此操作会堵塞
假如T1
持有行R的X
锁,那么T2
恳求R的X、S
锁都不能被当即答应,T2
有必要等候T1
开释X
锁才能够,由于X
锁与任何的锁都不兼容。
S锁和X锁的兼容关系如下图表格:
X
锁和S
锁是关于行记载来说的话,因而能够称它们为行级锁或许行锁。咱们以为行锁的粒度就比较细,其实一个业务也能够在表等级下加锁,对应的,咱们称之为表锁。给表加的锁,也是能够分为X
锁和S
锁的哈。
假如一个业务给表已经加了S
锁,则:
- 其他业务能够持续取得该表的
S
锁,也能够取得该表中某些记载的S
锁。 - 其他业务不能够持续取得该表的
X
锁,也不能够取得该表中某些记载的X
锁。
假如一个业务给表加了X
锁,那么
- 其他业务不能够取得该表的
S
锁,也不能够取得该表某些记载的S
锁。 - 其他业务不能够取得该表的
X
锁,也不能够持续取得该表某些记载的X
锁。
2.2 意向锁
什么是意向锁呢?意向锁是一种不与行级锁抵触的表级锁。未来的某个时刻,业务可能要加同享或许排它锁时,先提早声明一个意向。注意一下,意向锁,是一个表级其他锁哈。
为什么需求意向锁呢? 或许换个浅显的说法,为什么要加同享锁或排他锁时的时分,需求提早声明个意向锁呢呢?
由于InnoDB是支撑表锁和行锁共存的,假如一个业务A获取到某一行的排他锁,并未提交,这时分业务B恳求获取同一个表的表同享锁。由于同享锁和排他锁是互斥的,因而业务B想对这个表加同享锁时,需求确保没有其他业务持有这个表的表排他锁,一起还要保证没有其他业务持有表中恣意一行的排他锁。
然后问题来了,你要确保没有其他业务持有表中恣意一行的排他锁的话,去遍历每一行?这样显然是一个效率很差的做法。为了处理这个问题,InnoDb的规划大叔提出了意向锁。
意向锁是怎么处理这个问题的呢? 咱们来看下
意向锁分为两类:
- 意向同享锁:简称
IS
锁,当业务预备在某些记载上加S锁时,需求现在表等级加一个IS
锁。 - 意向排他锁:简称
IX
锁,当业务预备在某条记载上加上X锁时,需求现在表等级加一个IX
锁。
比方:
-
select ... lock in share mode
,要给表设置IS
锁; -
select ... for update
,要给表设置IX
锁;
意向锁又是怎么处理这个效率低的问题呢:
假如一个业务A获取到某一行的排他锁,并未提交,这时分表上就有
意向排他锁
和这一行的排他锁
。这时分业务B想要获取这个表的同享锁,此时由于检测到业务A持有了表的意向排他锁
,因而业务A必然持有某些行的排他锁,也便是说业务B对表的加锁恳求需求堵塞等候,不再需求去检测表的每一行数据是否存在排他锁啦。
意向锁仅仅标明意向的锁,意向锁之间不会互斥,是能够并行的,整体兼容性如下:
2.3 记载锁(Record Lock)
记载锁是最简略的行锁,仅仅锁住一行。如:SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE
,假如C1字段是主键或许是仅有索引的话,这个SQL会加一个记载锁(Record Lock)
记载锁永远都是加在索引上的,即使一个表没有索引,InnoDB也会隐式的创建一个索引,并运用这个索引施行记载锁。它会堵塞其他业务对这行记载的刺进、更新、删除。
一般咱们看死锁日志时,都是找关键词,比方lock_mode X locks rec but not gap
),就表明一个X型的记载锁。记载锁的关键词便是rec but not gap。以下便是一个记载锁的日志:
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;
2.4 空隙锁(Gap Lock)
为了处理幻读问题,InnoDB引进了空隙锁(Gap Lock)
。空隙锁是一种加在两个索引之间的锁,或许加在第一个索引之前,或最终一个索引之后的空隙。它锁住的是一个区间,而不仅仅是这个区间中的每一条数据。
比方lock_mode X locks gap before rec
表明X型gap锁。以下便是一个空隙锁的日志:
RECORD LOCKS space id 177 page no 4 n bits 80 index idx_name of table `test2`.`account`
trx id 38049 lock_mode X locks gap before rec
Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 3; hex 576569; asc Wei;;
1: len 4; hex 80000002; asc ;;
2.5 临键锁(Next-Key Lock)
Next-key锁是记载锁和空隙锁的组合,它指的是加在某条记载以及这条记载前面空隙上的锁。说得更具体一点便是:临键锁会封锁索引记载自身,以及索引记载之前的区间,即它的锁区间是前开后闭,比方(5,10]
。
假如一个会话占有了索引记载R的同享/排他锁,其他会话不能立刻在R之前的区间刺进新的索引记载。
If one session has a shared or exclusive lock on record R in an index, another session cannot insert a new index record in the gap immediately before R in the index order.
2.6 刺进意向锁
刺进意向锁,是刺进一行记载操作之前设置的一种空隙锁,这个锁开释了一种刺进方法的信号。 它处理的问题:多个业务,在同一个索引,同一个规模区间刺进记载时,假如刺进的方位不抵触,不会堵塞互相。
假设有索引值4、7,几个不同的业务预备刺进5、6,每个锁都在取得刺进行的独占锁之前用刺进意向锁各自锁住了4、7之间的空隙,但是不堵塞对方由于刺进行不抵触。以下便是一个刺进意向锁的日志:
RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000066; asc f;;
1: len 6; hex 000000002215; asc " ;;
2: len 7; hex 9000000172011c; asc r ;;...
锁形式兼容矩阵(横向是已持有锁,纵向是正在恳求的锁):
2.7 自增锁
自增锁是一种特殊的表等级锁。它是专门针对AUTO_INCREMENT
类型的列,关于这种列,假如表中新增数据时就会去持有自增锁。简言之,假如一个业务正在往表中刺进记载,一切其他业务的刺进有必要等候,以便第一个业务刺进的行,是接连的主键值。
官方文档是这么描绘的:
An AUTO-INC lock is a special table-level lock taken by transactions inserting into tables with AUTO_INCREMENT columns. In the simplest case, if one transaction is inserting values into the table, any other transactions must wait to do their own inserts into that table, so that rows inserted by the first transaction receive consecutive primary key values.
假设有表:
mysql> create table t0 (id int NOT NULL AUTO_INCREMENT,name varchar(16),primary key ( id));
mysql> show variables like '%innodb_autoinc_lock_mode%';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| innodb_autoinc_lock_mode | 1 |
+--------------------------+-------+
1 row in set, 1 warning (0.01 sec)
设置业务A和B替换履行流程如下:
经过上图咱们能够看到,当咱们在业务A中进行自增列的刺进操作时,其他会话业务B也进行刺进操作,这种状况下会产生2个古怪的现象:
- 业务A会话中的自增列如同直接增加了2个值。(如上图中过程7、8)
- 业务B会话中的自增列直接从2开端增加的。(如上图过程5、6)
自增锁是一个表等级锁,那为什么会话A业务还没完毕,业务B能够履行刺进成功呢?不是应该锁表嘛?这是由于在参数innodb_autoinc_lock_mode
上,这个参数设置为1
的时分,相当于将这种auto_inc lock
弱化为了一个更轻量级的互斥自增长机制去完结,官方称之为mutex
。
innodb_autoinc_lock_mode还能够设置为0或许2,
-
0:表明传统锁形式,运用
表级AUTO_INC
锁。一个业务的INSERT-LIKE
句子在句子履行完毕后开释AUTO_INC表级锁,而不是在业务完毕后开释。 -
1: 接连锁形式,接连锁形式关于
Simple inserts
不会运用表级锁,而是运用一个轻量级锁来生成自增值,由于InnoDB能够提早直到刺进多少行数据。自增值生成阶段运用轻量级互斥锁来生成一切的值,而不是一直加锁直到刺进完结。关于bulk inserts
类句子运用AUTO_INC表级锁直到句子完结。 -
2:交织锁形式,一切的
INSERT-LIKE
句子都不运用表级锁,而是运用轻量级互斥锁。
- INSERT-LIKE:指一切的刺进句子,包含: INSERT、REPLACE、INSERT…SELECT、REPLACE…SELECT,LOAD DATA等。
- Simple inserts:指在刺进前就能确认刺进行数的句子,包含:INSERT、REPLACE,不包含INSERT…ON DUPLICATE KEY UPDATE这类句子。
- Bulk inserts: 指在刺进钱不能确认行数的句子,包含:INSERT … SELECT/REPLACE … SELECT/LOAD DATA。
3. 什么是死锁?怎么防止死锁?
什么是死锁?
死锁是指两个或多个业务在同一资源上相互占用,并恳求确定对方的资源,从而导致恶性循环的现象。
怎么防止死锁?
- 尽量约好固定的顺序拜访表,由于穿插拜访更简略造成业务等候回路。
- 尽量防止大业务,主张拆成多个小业务。由于大业务占用的锁资源越多,越简略呈现死锁。
- 下降数据库阻隔等级,比方RR下降为RC,由于RR阻隔等级,存在GAP锁,死锁概率大许多。
- 死锁与索引是密不可分的,合理优化你的索引,死锁概率下降。
- 假如业务处理不好能够用分布式业务锁或许运用达观锁
4.数据库的达观锁和失望锁
失望锁:
失望锁她专注且缺少安全感了,她的心只归于当时业务,每时每刻都担心着它 心爱的数据可能被其他业务修正,所以一个业务具有(取得)失望锁后,其他任何业务都不能对数据进行修正啦,只能等候锁被开释才能够履行。
select…for update
便是MySQL失望锁的应用。
达观锁:
达观锁的“达观情绪”体现在,它以为数据的变化不会太频繁。因而,它答应多个业务一起对数据进行变化。完结方法:达观锁一般会经过version版本号/时刻戳判断记载是否被更改正,一般合作CAS算法完结。
我们能够看下这篇文章哈:图文并茂的带你彻底理解失望锁与达观锁
5. select for update加的是表锁还是行锁
这道面试题,一般需求分两种数据库阻隔等级(RR和RC),还需求分查询条件是仅有索引、主键、一般索引、无索引等几种状况分隔讨论
在RC阻隔等级下
- 假如查询条件是仅有索引,会加
IX
意向排他锁(表级其他锁,不影响刺进)、两把X
排他锁(行锁,别离对应仅有索引,主键索引) - 假如查询条件是主键,会加
IX
意向排他锁(表级其他锁,不影响刺进)、一把对应主键的X
排他锁(行锁,会锁住主键索引那一行)。 - 假如查询条件是一般索引,假如查询射中记载,会加
IX
意向排他锁(表锁)、两把X
排他锁(行锁,别离对应一般索引的X
锁,对应主键的X
锁);假如没有射中数据库表的记载,只加了一把IX
意向排他锁(表锁,不影响刺进) - 假如查询条件是无索引,会加两把锁,IX意向排他锁(表锁)、一把X排他锁(行锁,对应主键的X锁)。
查询条件是无索引,为什么不锁表呢? MySQL会走聚簇(主键)索引进行全表扫描过滤。每条记载都会加上X锁。但是,为了效率考虑,MySQL在这方面进行了改善,在扫描过程中,若记载不满足过滤条件,会进行解锁操作。一起优化违反了2PL准则“`。
在RR阻隔等级
- 假如查询条件是仅有索引,射中数据库表记载时,总共会加三把锁:一把IX意向排他锁 (表锁,不影响刺进),一把对应主键的X排他锁(行锁),一把对应仅有索引的X排他锁 (行锁)。
- 假如查询条件是主键,会加
IX
意向排他锁(表级其他锁,不影响刺进)、一把对应主键的X
排他锁(行锁,会锁住主键索引那一行)。 - 假如查询条件是一般索引,射中查询记载的话,除了会加X锁(行锁),IX锁(表锁,不影响刺进),还会加Gap 锁(空隙锁,会影响刺进)。
- 假如查询条件是无索引,会加一个IX锁(表锁,不影响刺进),每一行实际记载行的X锁,还有对应于supremum pseudo-record的虚拟全表行锁。这种场景,浅显点讲,其实便是锁表了。
我们能够看下我之前写的这篇文章哈:聊聊select for update到底加了什么锁
6.怎么运用数据库分布式锁
一般能够运用select ... for update
来完结数据库的分布式锁。它的优点是:简略,运用方便,不需求引进Redis、zookeeper等中间件。缺陷是:不适合高并发的场景,db操作功能较差。
我之前这篇文章,有具体介绍数据库分布式锁的完结,我们能够看看哈:面试必备:聊聊分布式锁的多种完结!
7.表级锁和行级锁,页面锁有什么差异
不同的存储引擎:
- MyISAM存储引擎,它只支撑表锁,并发写的时分,功能差。
- InnoDB存储引擎,即支撑表锁,也支撑行锁,默以为行级锁。
- BDB 存储引擎,它支撑表锁和页锁。
表级锁和行级锁的差异:
- 表锁:开支小,加锁快;确定力度大,产生锁抵触概率高,并发度最低;不会呈现死锁。
- 行锁: 开支大,加锁慢;会呈现死锁;确定粒度小,产生锁抵触的概率低,并发度高,行级锁能大大减少数据库操作的抵触
- 页面锁:开支和加锁时刻界于表锁和行锁之间;会呈现死锁;确定粒度界于表锁和行锁之间,并发度一般。
8. Mysql一条SQL是怎么加锁的?
一条SQL加锁,能够分9种状况进行:
- 组合一:id 列是主键,RC 阻隔等级
- 组合二:id 列是二级仅有索引,RC 阻隔等级
- 组合三:id 列是二级非仅有索引,RC 阻隔等级
- 组合四:id 列上没有索引,RC 阻隔等级
- 组合五:id 列是主键,RR 阻隔等级
- 组合六:id 列是二级仅有索引,RR 阻隔等级
- 组合七:id 列是二级非仅有索引,RR 阻隔等级
- 组合八:id 列上没有索引,RR 阻隔等级
- 组合九:Serializable 阻隔等级
能够看我这篇文章,第3末节有具体讲到哈:两万字详解!InnoDB 锁专题!
9. 并发状况下,怎么做到安全的修正同一行数据
要安全的修正同一行数据,就要确保一个线程在修正时其它线程无法更新这行 记载。其实一般有失望锁和达观锁两种思维,
失望锁思维便是,当时线程要进来修正数据时,其他线程都得拒之门外~ 比方,
能够运用 select…for update ~
,
select * from User where name=‘jay’ for update
以上这条 sql 句子会确定了User
表中一切符合检索条件(name=‘jay’)的记
录。本次业务提交之前,其他线程都无法修正这些记载。
还能够运用达观锁思维:
所谓达观锁思维,便是有线程过来,先放过去修正,假如看到其他线程没修正过, 就能够修正成功,假如其他线程修正过,就修正失败或许重试。完结方法:乐 观锁一般会运用版本号机制或 CAS 算法完结。能够看一下我这篇文章,主要是思路哈~ CAS 达观锁处理并发问题的一次实践
当然,答复这个问题的时分呢,你能够说到分布式锁。分布式锁有这三种完结方法:
- 数据库分布式锁
- Redis分布式锁
- Zookeeper分布式锁
我们能够看我这篇文章哈:面试必备:聊聊分布式锁的多种完结!
10. RR阻隔等级下的加锁规矩是怎么样的?
其实,极客时刻丁奇的MySQL45讲有讲到,RR阻隔等级,是怎么加锁的。我们有兴趣能够去订货看下哈,十分不错的课程。
首先MySQL的版本,是5.x 系列 <=5.7.24,8.0 系列 <=8.0.13
。加锁规矩总共包含:两个准则、两个优化和一个bug。
- 准则1:加锁的基本单位都是
next-key lock
。next-key lock(临键锁)是前开后闭区间。 - 准则2:查找过程中拜访到的对象才会加锁。
- 优化1:索引上的等值查询,给仅有索引加锁的时分,
next-key lock
退化为行锁(Record lock)
。 - 优化 2:索引上的等值查询,向右遍历时且最终一个值不满足等值条件的时分,
next-key lock
退化为空隙锁(Gap lock)
。 - 一个 bug:仅有索引上的规模查询会拜访到不满足条件的第一个值为止。
我们能够看我这篇文章,第4末节有具体讲到哈:两万字详解!InnoDB 锁专题!
11. InnoDB 中行级锁时怎么完结的?InnoDB三种行锁的算法
MySQL上的行级锁是经过给索引上的索引项加锁来完结,只有经过索引条件检索数据,InnoDB才运用行级锁,否则,InnoDB将运用表锁。一起,当两条不同行的记载运用了相同的索引键时,也是会产生锁抵触的。
比方这条SQL:
select * from t where id = 666 for update;复制代码
for update
能够依据条件来完结行锁确定,而且 id 是有索引键的列,假如 id 不是索引键那么InnoDB将实施表锁。
InnoDB行锁的3种算法:
- Record Lock:单个索引记载上的锁,
- Gap Lock :空隙锁,确定一个规模,但不包含记载自身
- Next-Key Lock:它等于
Gap Lock + Record Lock
,确定一个规模,而且确定记载自身。
假如查询条件的是仅有索引,或许主键时,Next-Key Lock
会降为Record Lock
。假如是一般索引,将对下一个键值加上gap lock
,其实便是对下一个键值的规模为加锁。gap lock
空隙锁,便是为了处理幻读问题而规划出来的。
空隙锁是RR阻隔级其他,假如你想关闭空隙锁,你能够修正阻隔等级。也能够修正这个数据库参数innodb_locks_unsafe_for_binlog
为1
.
12. MySQL 遇到过死锁问题吗,你是怎么处理的?
我排查死锁的一般过程是酱紫的:
- 检查死锁日志
show engine innodb status
; - 找出死锁
Sql
- 剖析
sql
加锁状况 - 模仿死锁案发
- 剖析死锁日志
- 剖析死锁结果
能够看我这两篇文章哈:
- 手把手教你剖析Mysql死锁问题
- 两万字详解!InnoDB锁专题!
最终
我是捡田螺的小男孩,我们假如觉得面试题的答案有不对的当地,在谈论去提出来哈,一起进步,加油~ 假如觉得文章对你有协助,费事给个三连支撑一下哈,感谢支撑。