
Insert Intention Lock(插入意向锁)
什么是插入意向锁
WHAT
翻译一下这段话。大概意思是说:Insert Intention Lock(插入意向锁)是一种Gap锁(间隙锁遗忘了的同学可移步至MySQL之锁详解(三):InnoDB的Record锁、Gap锁和Next-Key锁),发生在Insert插入操作数据行之前。多个事务以同样的方式在同一个索引同一个范围区间插入记录时,如果插入位置不冲突,不会彼此阻塞。假设有值为4和7的索引记录。分别尝试插入值5和6的独立事务,每个事务在获得插入行上的排他锁之前,都会用Insert intention Lock(插入意向锁)锁定4和7之间的间隙,但不会阻塞彼此,因为行是不冲突的。
插入意向锁是如何加锁的
HOW
mysql> SET tx_isolation='REPEATABLE-READ';
mysql> SELECT @@tx_isolation;
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);
mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;复制
mysql> SET tx_isolation='REPEATABLE-READ';
mysql> SELECT @@tx_isolation;
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);复制
第二个会话,我们同样设置了RR事务隔离级别,然后插入id=101的数据,发现被阻塞了。看下加锁情况:
mysql> SHOW ENGINE INNODB STATUS\G
复制
通过锁信息我们可知。因第一个会话id>100 for update加了范围X锁(排他锁),第二个会话的事务是一个Insert语句,锁的类型是lock_mode X locks gap ... insert Intention(排他的间隙插入意向锁),又因Insert的值正好处在第一个事务的加锁范围内,所以第二个事务的发生了锁等待。所以,第二个会话会被阻塞。
当我们将第一个会话回滚(即释放了会话1的锁):
mysql> ROLLBACK;
复制
第二个会话被阻塞的SQL就可以正常提交了。然后,将第二个会话的事务也提交。重新开启两个会话、开启事务、同时插入不同的数据:
# 会话1:
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (111);
mysql> INSERT INTO child (id) VALUES (113);
# 会话2:
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (112);
mysql> INSERT INTO child (id) VALUES (114);复制
mysql> SHOW ENGINE INNODB STATUS\G
复制
你会发现,此时是没有锁的。至此得到结论:多个事务中执行相同格式的插入语句在同一个索引同一个范围区间插入记录时,只要插入的记录互相不冲突,就不会被阻塞,且不显示相关Insert Intention Lock(插入意向锁)信息。
继续,我们让两个会话同时插入相同的数据,看看会发生什么:
# 会话1:
mysql> INSERTINTOchild (id) VALUES (115);
# 会话2:
mysql> INSERTINTOchild (id) VALUES (115);复制
隐式锁和显式锁
隐式锁
implicit-lock
通过上面官档给出的栗子🌰,我们证明了Insert Intention Lock(插入意向锁)的存在。但是在开启两个会话、同时开启事务、同时Insert插入不同数据的时候,除了IX(table-level)意向排他锁之外,是看不到Insert Intention Lock(插入意向锁)或者说任何锁信息的身影。这就有点奇怪了,既然Insert Intention Lock(插入意向锁)是存在的,为什么有时候又不显示锁信息呢?
带着上面的疑问,我们来看一个栗子🌰,同样使用官档中的child表,我们重新开启一个会话:
mysql> SET tx_isolation='REPEATABLE-READ';
mysql> SELECT @@tx_isolation;
mysql> BEGIN;
mysql> INSER TINTO child (id) VALUES (16);
mysql> SHOW ENGINE INNODB STATUS\G复制
mysql> SET tx_isolation='REPEATABLE-READ';
mysql> SELECT @@tx_isolation;
mysql> BEGIN;
mysql> SELECT * FROM child WHERE id = 16 FOR UPDATE;
mysql> SHOW ENGINE INNODB STATUS\G复制
第一部分:因为会话1执行Insert时是没有任何锁信息的,但是会话2的SELECT ... WHERE id=16 for update;语句正在等待id=16这行记录的锁,是不是感觉怪怪的(会话1本来没有锁,为什么会话2会被阻塞)? 第二部分:阻塞会话2的这把锁被显示出来了,lock_mode X locks rec but not gap,这个锁看起来有点莫名其妙是不是?
显式锁
explicit-lock
对于INSERT操作来说,一开始就是不创建锁的对象(其实加的是隐式锁),只有当发生等待的时候才会转换为显示的锁。
栗子🌰中id=16这条记录,在活跃事务列表(ReadView)中,说明没有提交,上面有锁。这时候再去创建锁的对象(延迟去创建锁的对象),在延迟过程中,没有对这条记录加锁的话,就不用创建锁的对象了,这样就可以节省内存。但是查看锁信息的时候还是没有出现insert intention lock(插入意向锁)的信息,而是显示lock_mode X locks rec but not gap的信息。这一点和文章刚开始显式的lock_mode X locks gap ... insert Intention(排他的间隙插入意向锁)信息有点差异,大家可以再回过头去对比一下。
为了便于大家理解,我再举一个栗子🌰,给大家看一下:
# Time1,事务1
mysql> SET tx_isolation='REPEATABLE-READ';
mysql> BEGIN;
mysql> SELECT * FROM child WHERE id <= 100 FOR UPDATE;
# Time2,事务2
mysql> SET tx_isolation='REPEATABLE-READ';
mysql> BEGIN;
mysql> INSERT INTO child (id) VALUES (95);
# Time3,事务1
mysql> COMMIT;
# Time4,事务2
mysql> INSERT INTO child (id) VALUES (95);成功插入复制
Time2时,插入95会被阻塞,看下锁信息:
首先会话1加的(93,100]的Next-Key Lock; 然后会话2加的是:Gap + Insert Intention Lock(间隙插入意向锁); 当会话1事务提交,锁释放,会话2就持有了这把锁,这把锁比较特殊,(95,100)的Insert Intention的Gap锁。
# Time5,事务3
mysql> SET tx_isolation='REPEATABLE-READ';
mysql> BEGIN;
mysql> INSERT INTO child (id) VALUES (96);成功插入复制
可以看到,Time5事务3成功插入(即使会话2持有(95,100)的Insert Intention的Gap锁)。
插入意向锁小结
SUMMARY
Insertion Intention Lock(插入意向锁),就是为了提升并发插入能力的。
但是上述的Gap锁(95,100),应该是不能插入96的,但是这个锁比较特殊,是Gap Insert Intention Llock,插入是允许的。意义在于:提升了插入的性能,如果没有Insertion Intention Lock,那(95,100)就是Gap锁,如果持有的话,96就不能插入,性能就不好了。
Transaction2插入95,锁100的意义:首先Transaction1是(93,100]的Next-Key Lock,所以Transaction2插入95的时候就会被阻塞(因为95要在100上面加一个Gap锁,要知道自己能不能插入:一条记录能不能插入,就要看它插入记录后面的记录有没有锁,锁是否为Gap的,如果是,不能插。如果是Record锁,可以插入。),所以要插入95的时候,一定要在100上面加一个Gap锁。如果只加一个Gap锁,当Transaction1提交了,(93,100]锁释放了,100这上面的Gap锁锁住的就是(95,100)的Gap,如果插入96就不允许了。但是我加的是Gap Insert Intention Lock,就可以插入,这就是Gap Insertion Intention Lock存在的意义:用来判断我当前的事务能不能被插入,但是不会阻塞后面的插入操作。插入意向锁和插入意向锁之间是兼容的,只阻塞我当前线程的插入,不阻塞其他会话的插入。
AUTO-INC Lock(自增锁)
什么是自增锁
WHAT
我们还是先来看下MySQL官档中对AUTO-INC Lock(自增锁)的描述(https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html)。
翻译总结一下:AUTO-INC锁(自增锁)是一种特殊的(table-level)表级锁,专门针对具有AUTO_INCREMENT属性(MySQL之字段属性)的字段数据插入时获得。同一张表,如果一个事务正在向表中插入值,那么任何其他事务都必须等待自己对该表进行插入,以便第一个事务插入的行接收连续的主键值。即事务1正在插入数据,事务2也插入数据,此时事务2会被阻塞,直到事务1释放AUTO-INC Lock(自增锁),其目的就是为了保证事务1插入的记录是连续的。
插入类型
TYPE
插入类型 | 说明 |
---|---|
Insert-like | Insert-like指所有的插入语句,如INSERT、REPLACE、INSERT ... SELECT、LOAD DATA等 |
Simple Inserts(简单插入) | Simple Insert指能在插入前就确定插入行数的语句。这些语句包含INSERT、REPLACE等。需要注意的是:Simple Inserts不包含INSERT ... ON DUPLICATE KEY UPDATE这类SQL |
Bulk Inserts(批量插入) | 打开表Bulk Inserts指在插入前不能确定得到插入行数的语句,如INSERT ... SELECT,REPLACE ... SELECT,LOAD DATA |
Mixed-mode Inserts(混合插入) | Mixed-mode Inserts指插入中有一部分的值是自增长的,有一部分是确定的。比如INSERT INTO table(id,name) VALUES(1,'zhangsan'),(2,'lisi'),(3,'wangwu');也可以是指INSERT ... ON DUPLICATE KEY UPDATE这类SQL语句 |
自增锁的模式
MODE
从MySQL 5.1.22版本开始,InnoDB存储引擎中提供了一种轻量级互斥量的自增长实现机制,这种机制大大提高了自增长值插入的性能。从这个版本开始,InnoDB存储引擎提供了一个参数innodb_autoinc_lock_mode来控制自增长的模式,共有3个可选项,即0,1,2。
innodb_autoinc_lock_mode | 说明 |
---|---|
0 | MySQL 5.1.22版本之前的自增长的实现方式,即通过表锁AUTO-INC Lock方式 |
1 | 该参数的默认值。对于“Simple Inserts”,该值会用互斥量(mutex)去对内存中的计数器进行累加的操作。对于“Bulk Inserts”,还是使用传统表锁AUTO-INC Lock方式。在这种配置下,如果不考虑回滚操作,对于自增值列的增长还是连续的,并且在这种方式下,Binlog格式为statement(SBR)的复制操作还是能很好的工作。需要注意:如果已经使用AUTO-INC Lock方式去产生自增长的值,而这时需要再进行“Simple Inserts”操作时,还是需要等待AUTO-INC Lock的释放 |
2 | 在这个模式下,对于所有的“Insert-like”自增长的值的产生都是通过互斥量,而不是AUTO-INC Lock的方式。显然,这是最佳性能模式,但同时,也会带来一定问题。因为并发插入的问题,在每次插入时,自增长的值可能不是连续的。此外,最重要的是,基于statement(SBR)格式的复制会出现问题,因此,使用这个模式的复制,必须使用基于ROW(RBR)。这样才能保证最大的并发性能及主从一致 |
mysql> CREATE TABLE a (
-> col1 INT AUTO_INCREMENT,
-> col2 INT,
-> KEY(col2,col1)
-> ) ENGINE = InnoDB;
ERROR 1075 (42000): Incorrect table definition; there can be only one auto column and it must be defined as a key
mysql>
mysql> CREATE TABLE b (
-> col1 INT AUTO_INCREMENT,
-> col2 INT,
-> KEY(col2,col1)
-> ) ENGINE = MyISAM;
Query OK, 0 rows affected (0.02 sec)复制
小结
Insert Intention Lock(插入意向锁)是一种隐式的Gap锁,只有当发生锁冲突时,才会将隐式锁转为显示锁; 多个事务中执行相同格式的插入语句在同一个索引同一个范围区间插入记录时,只要插入的记录互相不冲突,就不会被阻塞,因为隐式锁的原因,不会显示相关锁信息; Insertion Intention Lock(插入意向锁),就是为了提升并发插入能力的。用来判断我当前的事务能不能被插入,但是不会阻塞后面的插入操作。插入意向锁和插入意向锁之间是兼容的,只阻塞我当前线程的插入,不阻塞其他会话的插入; AUTO-INC锁(自增锁)是一种特殊的(table-level)表级锁,专门适用于具有AUTO_INCREMENT属性字段数据插入操作时; AUTO-INC Lock(自增锁)保证了同一张表,同一个事务种插入自增数据的连续性,在此期间,任何其他事务都必须等待自己操作完成才可继续操作; 参数innodb_autoinc_lock_mode用来控制自增长的模式,可选参数1,2,3,设置为不同值时,可能存在自增值不连续的情况。




参考资料
姜承尧《MySQL技术内幕:InnoDB存储引擎 第2版》
https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html





扫描二维码关注
获取更多精彩
GrowthDBA



end