Mysql七种锁

  • Post author:
  • Post category:mysql


MySQL是目前世界上最流行的数据库,InnoDB是MySQL最流行的存储引擎,它在大数据量高并发量的业务场景下,有着非常良好的性能表现,之所以如此,是和InnoDB的锁机制相关。

总的来说,InnoDB共有七种类型的锁:

(1)自增锁(Auto-inc Locks);

(2)共享/排它锁(Shared and Exclusive Locks);

(3)意向锁(Intention Locks);

(4)插入意向锁(Insert Intention Locks);

(5)记录锁(Record Locks);

(6)间隙锁(Gap Locks);

(7)临键锁(Next-key Locks);


第一种,自增锁(Auto-inc Locks)


【案例说明】

MySQL,InnoDB,默认的隔离级别(RR),假设有数据表:


t(id AUTO_INCREMENT, name);

数据表中有数据:


1, wangwu


2, zhangsan


3, lisi

事务A先执行,还未提交:


insert into t(name) values(xxx);

事务B后执行:


insert into t(name) values(ooo);


问:事务B会不会被阻塞?


【案例分析】

InnoDB在RR隔离级别下,尝试解决幻读问题,上面这个案例中:

(1)事务A先执行insert,会得到一条(4, xxx)的记录,由于是自增列,故不用显示指定id为4,InnoDB会自动增长,注意此时事务并未提交;

(2)事务B后执行insert,假设不会被阻塞,那会得到一条(5, ooo)的记录;

此时,并未有什么不妥,但如果,

(3)事务A继续insert:


insert into t(name) values(xxoo);

会得到一条(6, xxoo)的记录。

(4)事务A再select:


select * from t where id>3;

得到的结果是:


4, xxx


6, xxoo


画外音:不可能查询到5的记录,在RR的隔离级别下,不可能读取到还未提交事务生成的数据。

这对于事务A来说,就很奇怪了,AUTO_INCREMENT的列,连续插入了两条记录,一条是4,接下来一条变成了6,就像莫名其妙的幻影。


【自增锁】

自增锁是一种特殊的表级别锁(table-level lock),专门针对事务插入AUTO_INCREMENT类型的列。

最简单的情况,如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。

与此同时,InnoDB提供了innodb_autoinc_lock_mode配置,可以调节与改变该锁的模式与行为。


第二种,共享/排它锁

(Shared and Exclusive Locks)

(1)事务拿到某一行记录的共享S锁,才可以读取这一行;

(2)事务拿到某一行记录的排它X锁,才可以修改或者删除这一行;

其兼容互斥表如下:

即:

(1)多个事务可以拿到一把S锁,读读可以并行;

(2)而只有一个事务可以拿到X锁,写写/读写必须互斥;

共享/排它锁的潜在问题是,不能充分的并行,解决思路是数据多版本


第三种,意向锁(Intention Locks)

InnoDB支持多粒度锁(multiple granularity locking),它允许行级锁与表级锁共存,实际应用中,InnoDB使用的是意向锁。

意向锁是指,未来的某个时刻,事务可能要加共享/排它锁了,先提前声明一个意向。


意向排它锁



(intention exclusive lock, IX)



,它预示着,事务有意向对表中的某些行加排它X锁

select * from demo where id = 1 for update

上述语句执行的时候会对demo这张表添加一个表级别的意向排它锁,并且在id=1的记录上加一个行级别排它锁。


意向共享锁



(intention shared lock, IS)



,它预示着,事务有意向对表中的某些行加共享S锁

select * from demo where id = 1

上述语句执行的时候会对demo这张表添加一个表级别的意向共享锁,并且在id=1的记录上加一个行级别共享锁。


第四种,插入意向锁



(Insert Intention Locks)

对已有数据行的修改与删除,必须加强互斥锁X锁,那对于数据的插入,是否还需要加这么强的锁,来实施互斥呢?插入意向锁,孕育而生。

插入意向锁,是间隙锁

(Gap Locks)

的一种(所以,也是实施在索引上的),它是专门针对insert操作的。


1、插入意向锁是Gap锁,不是意向锁,是insert操作产生的。当多个事务同时将不同的数据写入同一个索引间隙时,不需要等待其他事务完成,也不会发生锁等待。


假定有一个记录索引包含键值4和7,不同的事务分别插入5和6,每个事务都会产生一个插入意向锁,加到4-7之间,得到插入行上的排他锁,但不会相互锁定,因为数据行并不冲突。


2. 插入意向锁不会阻止任何锁,插入记录会持有记录锁。



insert into test_user(user_id,name,age) values(2,’b’,10)——- TRX HAS BEEN WAITING 18 SEC FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 6628 page no 4 n bits 72 index `index_user` of table `test`.`test_user` trx id 117851203插入意向锁 lock_mode X insert intention waitingRecord lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;;


以上就是mysql插入意向锁的使用


第五种,记录锁



(Record Locks)

记录锁,它封锁索引记录,例如:



select * from t where id=1 for update;

它会在id=1的索引记录上加锁,以阻止其他事务插入,更新,删除id=1的这一行。

需要说明的是:



select * from t where id=1;

是快照读(SnapShot Read),它并不加锁


第六种,间隙锁



(Gap Locks)


建表和初始化语句如下:


CREATE TABLE `t` (


`id` int(11) NOT NULL,


`c` int(11) DEFAULT NULL,


`d` int(11) DEFAULT NULL,


PRIMARY KEY (`id`),


KEY `c` (`c`)


) ENGINE=InnoDB;


insert into t values(0,0,0),(5,5,5),


(10,10,10),(15,15,15),(20,20,20),(25,25,25);

这个表除了主键id外,还有一个索引c

为了解决幻读问题,InnoDB引入了间隙锁,锁的就是两个值之间的空隙

当执行

select * from t where d=5 for update

的时候,就不止是给数据库中已有的6个记录加上了行锁,还同时加了7个间隙锁。这样就确保了无法再插入新的记录

行锁分成读锁和写锁


跟间隙锁存在冲突关系的是往这个间隙中插入一个记录这个操作。间隙锁之间不存在冲突关系

这里sessionB并不会被堵住。因为表t里面并没有c=7会这个记录,因此sessionA加的是间隙锁(5,10)。而sessionB也是在这个间隙加的间隙锁。它们用共同的目标,保护这个间隙,不允许插入值。但它们之间是不冲突的

间隙锁和行锁合称next-key lock,每个next-key lock是前开后闭区间。表t初始化以后,如果用select * from t for update要把整个表所有记录锁起来,就形成了7个next-key lock,分别是(-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。因为+∞是开区间,在实现上,InnoDB给每个索引加了一个不存在的最大值supremum,这样才符合都是前开后闭区间

间隙锁和next-key lock的引入,解决了幻读的问题,但同时也带来了一些困扰


间隙锁导致的死锁:

1.sessionA执行select … for update语句,由于id=9这一行并不存在,因此会加上间隙锁(5,10)

2.sessionB执行select … for update语句,同样会加上间隙锁(5,10),间隙锁之间不会冲突

3.sessionB试图插入一行(9,9,9),被sessionA的间隙锁挡住了,只好进入等待

4.sessionA试图插入一行(9,9,9),被sessionB的间隙锁挡住了

两个session进入互相等待状态,形成了死锁

间隙锁的引入可能会导致同样的语句锁住更大的范围,这其实是影响并发度的

在读提交隔离级别下,不存在间隙锁


第七种,临键锁



(Next-Key Locks)

临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。

更具体的,临键锁会封锁索引记录本身,以及索引记录之前的区间。

如果一个会话占有了索引记录R的共享/排他锁,其他会话不能立刻在R之前的区间插入新的索引记录。



t(id PK, name KEY, sex, flag);

表中有四条记录:



1, songliu, m, A



3, zhangsan, m, A



5, lisi, m, A



9, wangwu, f, B

PK上潜在的临键锁为:



(-infinity, 1]



(1, 3]



(3, 5]



(5, 9]



(9, +infinity)

临键锁的主要目的,也是为了避免幻读

(Phantom Read)

。如果把事务的隔离级别降级为RC,临键锁则也会失效。


【总结】

(1)

自增锁


(Auto-inc Locks)

:表级锁,专门针对事务插入

AUTO_INC

的列,如果插入位置冲突,多个事务会阻塞,以保证数据一致性;

(2)

共享/排它锁


(Shared and Exclusive Locks)

:行级锁,S锁与X锁,强锁;

(3)

意向锁


(Intention Locks)

:表级锁,IS锁与IX锁,弱锁,仅仅表明意向;

(4)

插入意向锁


(Insert Intention Locks)

:针对insert的,如果插入位置不冲突,多个事务不会阻塞,以提高插入并发;

(5)

记录锁


(Record Locks)

:索引记录上加锁,对索引记录实施互斥,以保证数据一致性;

(6)

间隙锁


(Gap Locks)

:封锁索引记录中间的间隔,在RR下有效,防止间隔中被其他事务插入;

(7)

临键锁


(Next-key Locks)

:封锁索引记录,以及索引记录中间的间隔,在RR下有效,防止幻读;



版权声明:本文为songmulin原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。