理清MySQL的行锁、意向锁、记录锁、间隙锁和临键锁
在日常开发工作中,Mysql是常用的数据库之一,突然某天Mysql数据库告警提示出现了死锁问题,为了解决死锁问题,我们就需要掌握一些关于Mysql的锁的知识。
1、行锁
在InnoDB存储引擎中行级锁每次操作锁住对应的行数据,锁定粒度最小,发生锁冲突的概率最低,并发度最高。InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。在InnoDB存储引擎下实现了共享锁和排他锁这两种行锁,以下是两种锁的介绍:
(1)共享锁(简称:S)
允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。(加了共享锁之后可以读取,但是不可以写) ,典型是在查询后面添加for share。在Mysql的performance_schema下的data_locks表中记录关于锁的相关信息,记录锁信息的表位置所示的:
图片
执行如下的sql语句:
BEGIN;
#共享锁
SELECT * from stock where id = 8 FOR SHARE;
查询data_locks表的锁信息:
图片
S,REC_NOT_GAP:表示对id=8的数据添加一把读锁(S),其中REC_NOT_GAP表示锁的一个范围(是指到底去锁哪些数据),这里表示只锁住id=8的数据。
(2)排他锁(简称:X)
允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。(加了写锁之后其他的事务不可以添加任何的锁【读锁、写锁都不可以】)默认每次insert、update、delete的时候都是加排他锁,如下的更新sql:
BEGIN;
#排他锁
update stock set num= 81 where id = 8;
查询data_locks表的锁信息:
图片
X,REC_NOT_GAP:表示对id=8的数据添加一把排他锁(X),同样的REC_NOT_GAP表示锁的一个范围。
如果对select查询添加for update的时候,此时就是排他锁,如下的sql:
BEGIN;
#排他锁
SELECT * from stock where id = 8 FOR UPDATE;
查询data_locks表的锁信息:
图片
排他锁和共享锁的兼容性如下锁整理:
图片
在案例中我们使用的是主键id做为where的查询条件,假设我们现在不使用id而是使用一个非索引字段作作为查询的条件,sql如下所示:
BEGIN;
#共享锁
SELECT * from stock where name = 'A' FOR SHARE;
数据表中的现存的记录如下所示:
图片
执行sql后查询data_locks表的锁信息:
图片
我们可以发现目前锁类型就是表锁了。
2、记录锁
锁一条真实存在的记录(数据库中真实存在的数据),如下图是数据表中的数据记录:
图片
通过sql查询id=8的记录,sql如下所示:
BEGIN;
#共享锁
SELECT * from stock where id = 8 FOR SHARE;
锁的结果:
图片
3、间隙锁
间隙是指索引跟索引之间的间隙,假设现在查询id=5的数据(数据库中id为5的数据不存在),如下的数据表数据:
图片
执行如下的sql:
BEGIN;
#共享锁
SELECT * from stock where id = 5 FOR SHARE;
查询data_locks表的锁信息:
图片
S表示的读锁,GAP表示的间隙的意思,8代表的是一个节点(真实的记录),这里的含义是1-8之间的间隙是锁住的,这个间隙之内不可以添加数据,但是可以修改数据。
4、临键锁
临键锁是记录锁+间隙锁,因为在去加锁来锁数据的时候,那么可能既包含了区间也包含了一条真实的数据,假设数据表中的数据如下所示:
图片
现在执行sql:
BEGIN;
#共享锁
SELECT * from stock where id > 5 and id < 14 FOR SHARE;
查询data_locks表的锁信息:
图片
id=8这条数据的LOCK_MODE=S,它没有任何的标记,那么id=8这条数据就是临键锁(临键锁只标记了是X还是S);它表示既锁死了id=8这条数据,也锁死了id在1-8这个区间。
id=14这条数据中,它没有锁死id=14这个数据,只锁死了一个gap的区间。
5、意向锁
意向锁是为了提高粗粒度锁的性能而设置的一种预判机制(意向锁是为了协调行锁和表锁的关系,用于优化InnoDB加锁的策略),意向锁的主要作用是避免为了判断表是否存在行锁而去全表扫描(即在一个操作发起实际资源的锁申请行为之前,先对更粗力度的资源发起一个加锁意向声明),意向锁是由InnoDB在操作数据之前自动加的,不需要用户干预。如下所示的意向锁:
图片
意向锁分为意向共享锁(IS锁)【事务在请求S锁前,要先获得IS锁】;意向排他锁(IX锁)【事务在请求X锁前,要先获得IX锁】
意向锁(IS/IX)和X锁是冲突的,如下所示事务A执行语句:
BEGIN;
#共享锁
SELECT * from stock where id = 8 FOR SHARE;
事务B的执行语句:
BEGIN;
#排他锁
update stock set num= 140 where id = 14;
执行的效果图如下所示:
图片
①事务A首先申请整个表的IS锁(成功)。
图片
②事务A申请id=8这一行的S锁(成功)。
图片
③事务B申请整个表的IX锁(成功);因为IS和IX锁是兼容的,并且IX锁和行级别的S锁也是兼容的。
图片
④事务B申请整个表的X锁(成功);
图片
所以整个过程的数据库锁的信息:
图片
如果现在事务A给行记录id=8加共享锁成功后,事务B给id=8的行记录加排他锁,此时事务B就需要等待事务A释放锁才能加锁成功,如下图所示:
图片
数据库的锁信息如下所示:
图片
可以发现事务B此时在等待锁。
意向锁与其他锁的兼容性如下表整理:
图片
意向锁是一种高效的锁机制,特别适用于支持行级锁的数据库系统,能够在多事务并发访问的环境下有效地管理锁,提高系统的并发性和数据一致性。