innodb锁类型
- Share and Exclusive Locks(共享与排它锁)
- Intention Locks(意向锁)
- Record Locks (记录锁)
- Gap Locks(间隙锁)
- Next-Key Locks
- Insert Intention Locks(插入意向锁)
- AUTO-INC Locks(自增锁)
- Predicate Locks for Spatial Indexes
Share and Exclusive Locks
Innodb的行锁有两种类型,即S锁与X锁
- S锁:允许持有锁的事务去读取一行数据,显式加S锁:
SELECT ... LOCK IN SHARE MODE - X锁:允许持有锁的事务去修改一行数据,显式加X锁:SELECT ... LOCK FOR UPDATE
S锁与S锁互相兼容,X锁与X、S锁均不兼容。意思是假如事务T1持有行A的S锁,事务T2申请行A的S锁时可以立即获取,而申请X锁时则不能立刻获取。如果事务T1持有行A的X锁,那么其他事务对行A申请的S、X锁均不能立刻获取
Intention Locks
使用意向锁实现锁的多粒度(行粒度与表粒度),刚才说的X与S属于行级锁,那么意向锁则属于表级锁。表明事务要对表中的某些行使用X锁与S锁,所以意向锁也有2中类型
- IS锁:意味着要对表中某些行设置S锁
- IX锁:意味着要对表中某些行设置X锁
在获取一个行的S锁之前,必须先获取IS锁,在获取一个行的X锁之前,必须先获取IX锁
意向锁不会阻塞除了全表操作外的请求,他主要用作表明某个事务正在锁定或将要锁定表中的某些行
Record Locks
记录锁是索引记录上的锁,即行锁并不是直接锁记录,而是锁索引。
Gap Locks
锁定的是两条索引记录的间隙,或者是第一个索引之前,或者最后一个索引之后的间隙,所以又称间隙锁。Gap Lock的目的是为了阻止其他事务插入到间隙中。间隙锁可以共存。一个事务占用的间隙锁不会阻止另一个事务占用同一间隙上的间隙锁
Next-key Lock
是一个索引记录锁加上在索引记录之前的间隙上的间隙锁,即Record Lock与Gap Lock的组合。
假设我们有如下索引记录,10,11,13,那么可能的next-key锁区间为
(-无穷,10],(10,11],(11,13],(13,+无穷)
Insert Intention Locks
是Gap Lock的一种,是在插入操作之前的间隙锁。主要作用在于,多个事务插入到相同的索引间隙中,如果它们不在间隙中的相同位置插入,就不需要相互等待。
假设有值为4和7的索引记录。有两个事务尝试分别插入5和6,每个事务都用插入意图锁锁定4和7之间的间隙,但不会彼此阻塞。
CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
INSERT INTO child (id) values (90),(102);
START TRANSACTION;
SELECT * FROM child WHERE id > 100 FOR UPDATE;
START TRANSACTION;
INSERT INTO child (id) VALUES (101);
是否阻塞?
AUTO-INC Locks
自增锁,属于表级锁,用于生成自增ID。可以通过【innodb_autoinc_lock_mode】设置自增锁的模式,可以通过下面的文档来了解这个参数对于自增锁的影响。https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html
Predicate Locks for Spatial Indexes
用于空间索引上的锁,空间索引与传统B+树索引不相同,用处较少,暂不阐述
RR隔离级别加锁分析
了解了常见的锁结构,我们来分析下常见的SQL到底是如何加锁的,我们以MySQL默认隔离级别RR为例。
这里我们先开启下MySQL的锁信息监控
开启innodb锁监控信息:set GLOBAL innodb_status_output_locks=ON,开启之后我们就可以使用 SHOW ENGINE INNODB STATUS命令来查看语句的加锁信息了。
监控相关文档可以查看:https://dev.mysql.com/doc/refman/8.0/en/innodb-enabling-monitors.html
主键查询
- 主键等值查询
start TRANSACTION;
select * from lock where id = 1 for update;
这里主键记录存在,并且主键具有唯一性,所以这里比较简单,只给记录1加Record X锁。
start TRANSACTION;
select * from lock where id = 3 for update;
和上面不同,这里查询的记录不存在,那么此时给哪些记录加什么锁?我们SHOW ENGINE INNODB STATUS来看一下
------------
TRANSACTIONS
------------
Trx id counter 22488
Purge done for trx's n:o < 22487 undo n:o < 0 state: running but idle
History list length 48
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 281479539002944, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 281479539002040, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 22487, ACTIVE 52 sec
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 34554, OS thread handle 123145411301376, query id 1945591 localhost 127.0.0.1 root starting
show engine innodb status
TABLE LOCK table `test`.`lock` trx id 22487 lock mode IX
RECORD LOCKS space id 233 page no 3 n bits 72 index PRIMARY of table `test`.`lock` trx id 22487 lock_mode X locks gap before rec
Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
0: len 4; hex 00000004; asc ;;
1: len 6; hex 000000005797; asc W ;;
2: len 7; hex 210000035411f4; asc ! T ;;
3: len 4; hex 80000004; asc ;;
4: len 4; hex 80000004; asc ;;
通过日志能够看到这里加的是Gap锁,那么这里其实是对id=4的记录上加了一个gap锁,为的是防止后续的语句插入id=3的记录,产生幻读
start TRANSACTION;
update lock set col1 = 3 where id = 3;
比较简单,更新不存在的行,不加锁
start TRANSACTION;
update lock set col1 = 1 where id = 1;
更新不包含索引列,在主键记录上加Record X锁。
start TRANSACTION;
update lock set index1 = 1 where id = 1;
更新不包含索引列,在主键记录上加Record X锁,还需要在索引列上加Record X锁
- 主键范围查询
start TRANSACTION;
select * from `lock` where id >= 1 for update
会对id=1的记录加X锁,对id=4,id=8以及记录上界supremum加Next-Key锁,这样做到阻塞其他事务对id>=1的加锁操作。
start TRANSACTION;
select * from `lock` where id <= 1 for update
会对id=1的记录加X锁,对id=4,id=8加Next-Key锁,并且在判断id=4,8不符合记录后,虽然server层调用unlock_row,但对于RC隔离级别以上且没有设置innodb_locks_unsafe_for_binlog那么并不会释放锁。
start TRANSACTION;
update lock set col1=1 id >= 1
未更新索引列,加锁与加锁与上面SELECT… WHERE PK >= XX FOR UPDATE;一致。
start TRANSACTION;
update lock set index1=1 id >= 1
更新包含索引列。对主键id=1加X锁,index索引行加X锁,然后对c1=30,c1=40的主键行加next-key ,同时对应的index索引行加X锁,最后对表示记录上界的’supremum’加next-key
start TRANSACTION;
update lock set index1=1 id <= 4
对主键 in(1,4)加next-key ,同时对应的index索引行加X锁。然后对id=8加 next-key lock,因不满足条件,因此server层查询停止,但并不会释放id=8上的锁。
- 唯一索引等值
SELECT … WHERE UK = XX FOR UPDATE
记录存在,索引记录加X锁,对应主键加X锁。记录不存在,为了禁止幻读,需要保证别的事务不能再插入值为XX的新记录。需要在比XX大的第一条记录上加gap锁,这里只对二级索引记录进行加锁,并不会对聚簇索引记录进行加锁。
- 唯一索引范围
SELECT … WHERE UK >= 1 FOR UPDATE
那么会对索引行(假设index1为唯一索引) in (1, 4, 8)分别加next-key lock,对应主键行加X锁,同时对index1上’supremum’ record加next-key lock。
SELECT … WHERE UK <= 4 FOR UPDATE
和上面主键索引类似,这里会对索引上 in (1,4)加next-key lock,对对应的主键行加X锁,然后对index1=8加next-key lock,且并不会去释放。
UPDATE … WHERE UK >= XX;
未包含索引列时,等同上面指定走唯一索引的SELECT…FOR UPDATE语句加锁
包含索引列时,除了上述语句的加锁外,还会对相应索引列上的行加X锁。
UPDATE … WHERE UN <= 4;
更新未包含索引列
会对un索引上(1,4)加next-key lock,对对应的主键行加X锁。un=8对应的索引行和主键行也会加X锁,同时不会释放。
包含索引列
这里会对un索引上(1,4)加next-key lock,对对应的主键行和index2索引加X锁。对un=8加next-key lock,对应主键行加X锁,因不符合range条件,对index2不做操作不会加锁。
- 非唯一索引等值
SELECT … WHERE INDEX = 4 FOR UPDATE;
会对index =4在索引上加next-key lock,对应主键加X锁,然后在下一条记录上加gap锁 。
UPDATE … WHERE INDEX = XX;
未包含索引列时与上述一致;
包含索引列时,除了上述锁,会对相应的索引行加X锁。
- 不含索引列
SELECT * FROM WHERE col1 = 1 FOR UPDATE
这个比较简单,直接全表扫描,为主键记录加next-key 锁