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下有效,防止幻读;