innodb事务锁
根据文档innodb锁分为以下几种:
-
意向锁:
就是简单的IX,IS这类意向锁,这个容易理解,比如要在表上读取一行记录,那么表可能会被加IS锁,在表上写入就会被加IX锁
-
行锁:
这个也非常容易理解,就是在行上面加锁,S锁或者X锁 -
gap锁:
gap锁就是为了在repeatable read隔离级别下消除幻读引入的一种锁,打到serializable隔离级别的效果 -
next-key锁:
其实这个锁是有gap锁和record锁组成的,LOCK_ORDINARY 表示next-key lock 但是在8.0中已经看不出来了。 -
插入意向锁(INSERT_INTENTION)
插入意向锁是gap锁的一种,2个插入意向所gap锁并不会产生冲突。
repeatable read 隔离级别
在这个隔离级别下,innodb为了实现repeatable read消除幻读,达到serializable的效果就引入了gap lock。
next-key lock和gap lock
LOCK_ORDINARY 表示next-key lock 但是在8.0中已经看不出来了,在performance_schema.data_locks中是gap锁和record2条记录:
begin;
update id = 45 where i=40;
在另外一个会话中执行:
select thread_id,engine_transaction_id,object_name,index_name,lock_type,lock_mode,lock_status,lock_data from data_locks;
thread_id | engine_transaction_id | object_name | index_name | lock_type | lock_mode | lock_status | lock_data |
---|---|---|---|---|---|---|---|
240089 | 5446 | l | NULL | TABLE | IX | GRANTED | NULL |
240089 | 5446 | l | i | RECORD | X | GRANTED | 40, 50 |
240089 | 5446 | l | PRIMARY | RECORD | X,REC_NOT_GAP | GRANTED | 50 |
240089 | 5446 | l | i | RECORD | X,GAP | GRANTED | 60, 60 |
240089 | 5446 | l | i | RECORD | X,GAP | GRANTED | 40, 45 |
第2条和第5条组合其实就是一个next-key lock
第3条锁定primary 50 是因为原来的id=50
插入意向锁(INSERT_INTENTION lock)
在repeatable read隔离级别下,insert会先去判断一下 insert intention lock若成功,插入然后释放。且2个插入意向锁在同一个gap中并不会堵塞测试也很简单在上面事务的基础上执行语句:
insert into l values(60,35);
查看锁信息:
thread_id | engine_transaction_id | object_name | index_name | lock_type | lock_mode | lock_status | lock_data |
---|---|---|---|---|---|---|---|
240289 | 5447 | l | NULL | TABLE | IX | GRANTED | NULL |
240289 | 5447 | l | PRIMARY | RECORD | S,REC_NOT_GAP | GRANTED | 60 |
240289 | 5447 | l | i | RECORD | X,GAP,INSERT_INTENTION | WAITING | 40, 45 |
240089 | 5446 | l | NULL | TABLE | IX | GRANTED | NULL |
240089 | 5446 | l | i | RECORD | X | GRANTED | 40, 50 |
240089 | 5446 | l | PRIMARY | RECORD | X,REC_NOT_GAP | GRANTED | 50 |
240089 | 5446 | l | i | RECORD | X,GAP | GRANTED | 60, 60 |
240089 | 5446 | l | i | RECORD | X,GAP | GRANTED | 40, 45 |
而执行成功后锁会变成:
thread_id | engine_transaction_id | object_name | index_name | lock_type | lock_mode | lock_status | lock_data |
---|---|---|---|---|---|---|---|
240289 | 5454 | l | NULL | TABLE | IX | GRANTED | NULL |
也就是说插入意向锁已经被释放。
在2个会话中分别执行,发现并不会堵塞执行成功:
begin;
insert into l values(52,51);
begin;
insert into l values(51,50);
锁信息:
thread_id | engine_transaction_id | object_name | index_name | lock_type | lock_mode | lock_status | lock_data |
---|---|---|---|---|---|---|---|
240289 | 5454 | l | NULL | TABLE | IX | GRANTED | NULL |
240089 | 5453 | l | NULL | TABLE | IX | GRANTED | NULL |
read commit 隔离级别
因为innodb的特点,mysql的update并不会堵塞select语句。除非显示的提示加共享锁。
在read commit隔离级别下:
begin;
update l set id = 45 where i=40;
thread_id | engine_transaction_id | object_name | index_name | lock_type | lock_mode | lock_status | lock_data |
---|---|---|---|---|---|---|---|
240089 | 5455 | l | NULL | TABLE | IX | GRANTED | NULL |
240089 | 5455 | l | i | RECORD | X,REC_NOT_GAP | GRANTED | 40, 50 |
240089 | 5455 | l | PRIMARY | RECORD | X,REC_NOT_GAP | GRANTED | 50 |
和之前的相比上了lock_mode 少了带gap的标记,原先的x锁中也加了rec_not_gap。也就是说read commit不再锁定数据的gap。
堵塞判断
innodb堵塞判断因为有了gap,next-key,insert intention锁变的复杂(lock_rec_has_to_wait函数中体现):
- 首选判断意向锁,为此还定义了一个堵塞矩阵:
static const byte lock_compatibility_matrix[5][5] = {
/** IS IX S X AI */
/* IS */ { TRUE, TRUE, TRUE, FALSE, TRUE},
/* IX */ { TRUE, TRUE, FALSE, FALSE, TRUE},
/* S */ { TRUE, FALSE, TRUE, FALSE, FALSE},
/* X */ { FALSE, FALSE, FALSE, FALSE, FALSE},
/* AI */ { TRUE, TRUE, FALSE, FALSE, FALSE} };
-
即使步骤1冲突,根据不同的lock_mode 开始判断,以下情况不需要等待:
- 如果请求锁住的是 supremum 或者 LOCK_GAP 为 1 并且 LOCK_INSERT_INTENTION 为 0
- 如果请求锁 LOCK_INSERT_INTENTION 为 0 并且已有锁是 LOCK_GAP 为 1
- 如果请求锁 LOCK_GAP 为 1,请求锁 LOCK_REC_NOT_GAP 为 1
- 如果已有锁 LOCK_INSERT_INTENTION 为 1
参考:
MySQL · 引擎特性 · InnoDB 事务锁系统简介
MySQL · 引擎特性 · Innodb 锁子系统浅析