插入意向锁(Insert Intention Lock)
插入意向锁本质上可以看成是一个Gap Lock
- 普通的Gap Lock 不允许 在 (上一条记录,本记录) 范围内插入数据
- 插入意向锁Gap Lock 允许 在 (上一条记录,本记录) 范围内插入数据
插入意向锁的作用是为了提高并发插入的性能, 多个事务 同时写入 不同数据 至同一索引范围(区间)内,并不需要等待其他事务完成,不会发生锁等待。
插入的过程
假设现在有记录 10, 30, 50, 70 ;且为主键 ,需要插入记录 25 。
- 找到 小于等于25的记录 ,这里是 10
- 找到 记录10的下一条记录 ,这里是 30
- 判断 下一条记录30 上是否有锁
3.1 判断 30 上面如果 没有锁 ,则可以插入
3.2 判断 30 上面如果有Record Lock
,则可以插入
3.3 判断 30 上面如果有Gap Lock
/Next-Key Lock
,则无法插入,因为锁的范围是 (10, 30) /(10, 30] ;在30上增加insert intention lock
( 此时处于waiting状态),当 Gap Lock / Next-Key Lock 释放时,等待的事物( transaction)将被 唤醒 ,此时 记录30 上才能获得 insert intention lock ,然后再插入 记录25
注意:一个事物 insert 25 且没有提交,另一个事物 delete 25 时,记录25上会有 Record Lock
插入意向锁演示
数据准备
mysql> desc a;
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| b | int(11) | NO | PRI | NULL | |
+-------+---------+------+-----+---------+-------+
1 row in set (0.00 sec)
mysql> select * from a;
+----+
| b |
+----+
| 10 |
| 11 |
| 13 |
| 20 |
+----+
4 rows in set (0.00 sec)
开启两个会话,两个会话事务的隔离级别都设置为REPEATABLE-READ
Time | 会话A | 会话B |
---|---|---|
1 | begin | begin |
2 | select * from a where a<=13 for update | |
3 | insert into a values (12) -- waiting...... (被阻塞了,在这里等待) |
此时执行show engine innodb status\G
语句会看到以下结果
---TRANSACTION 4424, ACTIVE 7 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 3, OS thread handle 140018685810432, query id 240 localhost root update
--等待插入的SQL
insert into a values(12)
------- TRX HAS BEEN WAITING 7 SEC FOR THIS LOCK TO BE GRANTED:
--插入记录12的事物等待中(被终端会话A中的事物阻塞了),等待获得插入意向锁(lock_mode X locks gap before rec insert intention waiting)
RECORD LOCKS space id 37 page no 3 n bits 72 index PRIMARY of table `test`.`a` trx id 4424 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000d; asc ;;
1: len 6; hex 000000001140; asc @;;
2: len 7; hex b400000128011c; asc ( ;;
------------------
TABLE LOCK table `test`.`a` trx id 4424 lock mode IX
RECORD LOCKS space id 37 page no 3 n bits 72 index PRIMARY of table `test`.`a` trx id 4424 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000d; asc ;;
1: len 6; hex 000000001140; asc @;;
2: len 7; hex b400000128011c; asc ( ;;
---TRANSACTION 4423, ACTIVE 55 sec
2 lock struct(s), heap size 1136, 4 row lock(s)
MySQL thread id 2, OS thread handle 140018686076672, query id 241 localhost root starting
show engine innodb status
TABLE LOCK table `test`.`a` trx id 4423 lock mode IX
RECORD LOCKS space id 37 page no 3 n bits 72 index PRIMARY of table `test`.`a` trx id 4423 lock_mode X
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000113f; asc ?;;
2: len 7; hex b3000001270110; asc ' ;;
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000b; asc ;;
1: len 6; hex 000000001140; asc @;;
2: len 7; hex b4000001280110; asc ( ;;
Record lock, heap no 4 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000d; asc ;;
1: len 6; hex 000000001140; asc @;;
2: len 7; hex b400000128011c; asc ( ;;
Record lock, heap no 5 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000014; asc ;;
1: len 6; hex 000000001145; asc E;;
2: len 7; hex b70000012b0110; asc + ;;
Time | 会话A | 会话B |
---|---|---|
1 | begin | begin |
2 | select * from a where a<=13 for update | |
3 | insert into a values (12) -- waiting...... (被阻塞了,在这里等待) |
|
4 | commit | |
5 | 输出:Query OK, 1 row affected (17.40 sec) 前提条件是insert操作的锁没有超时 |
此时事务B插入成功但是还未commit,再执行show engine innodb status\G
语句,会有以下输出:
---TRANSACTION 4425, ACTIVE 26 sec
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 3, OS thread handle 140018685810432, query id 247 localhost root
TABLE LOCK table `test`.`a` trx id 4425 lock mode IX
RECORD LOCKS space id 37 page no 3 n bits 72 index PRIMARY of table `test`.`a` trx id 4425 lock_mode X locks gap before rec insert intention
Record lock, heap no 4 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000d; asc ;;
1: len 6; hex 000000001140; asc @;;
2: len 7; hex b400000128011c; asc ( ;;
从上面的输出可以看到在记录13上面加了一把插入意图锁(lock_mode X locks gap before rec insert intention
)。
获得插入意图锁之后,我们就可以在11-13之间并发插入记录,而不需要一个事物等待另一事物,当所有相关的插入的事物都提交后, 13上的插入意向锁 便会释放。
自增锁(AUTO-INC Locks)
在InnoDB中,每个含有自增列的表都有一个自增长计数器。当对含有自增长计数器的表进行插入时,首先会执行select max(auto_inc_col) from t for update
来得到计数器的值,然后再将这个值加1赋予自增长列。我们将这种方式称之为AUTO_INC Lock
。
AUTO_INC Lock
是一种特殊的表锁,它在完成对自增长值插入的SQL语句后立即释放,所以性能会比事务完成后释放锁要高。由于是表级别的锁,所以在并发环境下其依然存在性能问题。
从MySQL 5.1.22开始,InnoDB中提供了一种轻量级互斥量的自增长实现机制,同时InnoDB存储引擎提供了一个参数innodb_autoinc_lock_mode
来控制自增长的模式,进而提高自增长值插入的性能。innodb_autoinc_lock_mode
和插入类型有关,在介绍它之前,我们先来看看都有哪些插入类型
-
“INSERT-like” statements
泛指所有的插入语句, 它包括 “simple-inserts”, “bulk-inserts”, 和 “mixed-mode inserts”.
-
“Simple inserts”
插入的记录行数是确定的:比如:insert into values,replace
但是不包括: INSERT ... ON DUPLICATE KEY UPDATE. -
“Bulk inserts”
插入的记录行数不能马上确定的,比如: INSERT ... SELECT, REPLACE ... SELECT, and LOAD DATA
-
“Mixed-mode inserts”
这些都是simple-insert,但是部分auto increment值给定或者不给定. 例子如下(where
c1
is anAUTO_INCREMENT
column of tablet1
):INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
另外一种 “mixed-mode insert” 就是
INSERT ... ON DUPLICATE KEY UPDATE
介绍完插入类型之后,我们再来看看锁模式,即innodb_autoinc_lock_mode
。