InnoDB的锁机制浅析 (2)

事务加锁情况

mysql> show engine innodb status\G; ... ------------ TRANSACTIONS ------------ ---TRANSACTION 366811, ACTIVE 690 sec 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 2 MySQL thread id 785, OS thread handle 123145432457216, query id 729076 localhost 127.0.0.1 root ...

可以看到有一行被加了锁。由之前对锁的描述可以推测出,update语句给id=1这一行上加了一个X锁。

注意:X锁广义上是一种抽象意义的排它锁,即锁一般分为X模式和S模式,狭义上指row或者index上的锁,而Record锁是索引上的锁。
为了不修改数据,可以用select ... for update语句,加锁行为和update、delete是一样的,insert加锁机制较为复杂,后面的章节会提到。

第一个事务保持原状,不要提交或者回滚,现在开启第二个事务。

mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> update test set id=3 where id=1;

执行update时,sql语句的执行被阻塞了。查看下事务状态:

mysql> show engine innodb status\G; ... ------- TRX HAS BEEN WAITING 4 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 62 page no 3 n bits 72 index PRIMARY of table `test`.`test` trx id 366820 lock_mode X locks rec but not gap waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 32 0: len 8; hex 0000000000000001; asc ;; 1: len 6; hex 0000000598e3; asc ;; 2: len 7; hex 7e000001a80896; asc ~ ;; ------------------ ...

喜闻乐见,我们看到了这个锁的状态。状态标题是'事务正在等待获取锁',描述中的lock_mode X locks rec but not gap就是本章节中的record记录锁,直译一下'X锁模式锁住了记录'。后面还有一句but not gap意思是只对record本身加锁,并不对间隙加锁,间隙锁的叙述见下一个章节。

3.3 间隙锁 Gap Locks 间隙锁

间隙锁作用在索引记录之间的间隔,又或者作用在第一个索引之前,最后一个索引之后的间隙。不包括索引本身。
例如,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;这条语句阻止其他事务插入10和20之间的数字,无论这个数字是否存在。

间隙可以跨越0个,单个或多个索引值。

间隙锁是性能和并发权衡的产物,只存在于部分事务隔离级别。

select * from table where id=1;
唯一索引可以锁定一行,所以不需要间隙锁锁定。
如果列没有索引或者具有非唯一索引,该语句会锁定当前索引前的间隙。

在同一个间隙上,不同的事务可以持有上述兼容/冲突表中冲突的两个锁。例如,事务T1现在持有一个间隙S锁,T2可以同时在同一个间隙上持有间隙X锁。
允许冲突的锁在间隙上锁定的原因是,如果从索引中清除一条记录,则由不同事务在这条索引记录上的加间隙锁的动作必须被合并。

InnoDB中的间隙锁的唯一目的是防止其他事务插入间隙。
间隙锁是可以共存的,一个事务占用的间隙锁不会阻止另一个事务获取同一个间隙上的间隙锁。

如果事务隔离级别改为RC,则间隙锁会被禁用。

查看间隙锁

按照官方文档,where子句查询条件是唯一键且指定了值时,只有record锁,没有gap锁。
如果where语句指定了范围,gap锁是存在的。
这里只测试验证一下当指定非唯一键索引的时候,gap锁的位置,按照文档的说法,会锁定当前索引及索引之前的间隙。(指定了非唯一键索引,例如code=10,间隙锁仍然存在)

开启第一个事务,锁定一条非唯一的普通索引记录

mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from test where code = 10 for update; +----+------+ | id | code | +----+------+ | 10 | 10 | +----+------+ 1 row in set (0.00 sec)

由于预存了两条数据,row(1,1)和row(10,10),此时这个间隙应该是1<gap<10。我们先插入row(2,2)来验证下gap锁的存在,再插入row(0,0)来验证gap的边界。

按照间隙锁的官方文档定义,select * from test where code = 10 for update;会锁定code=10这个索引,并且会锁定code<10的间隙。

开启第二个事务,在code=10之前的间隙中插入一条数据,看下这条数据是否能够插入。

mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> insert into test values(2,2);

插入的时候,执行被阻塞,查看引擎状态:

mysql> show engine innodb status\G; ... ---TRANSACTION 366864, ACTIVE 5 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1 MySQL thread id 793, OS thread handle 123145434963968, query id 730065 localhost 127.0.0.1 root update insert into test values(2,2) ------- TRX HAS BEEN WAITING 5 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 63 page no 4 n bits 72 index code of table `test`.`test` trx id 366864 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 8; hex 800000000000000a; asc ;; 1: len 8; hex 000000000000000a; asc ;; ------------------ ...

插入语句被阻塞了,lock_mode X locks gap before rec,由于第一个事务锁住了1到10之间的gap,需要等待获取锁之后才能插入。

如果再开启一个事务,插入(0,0)

mysql> start transaction; mysql> insert into test values(0,0); Query OK, 1 row affected (0.00 sec)

可以看到:指定的非唯一建索引的gap锁的边界是当前索引到上一个索引之间的gap

最后给出锁定区间的示例,首先插入一条记录(5,5)

mysql> insert into test values(5,5); Query OK, 1 row affected (0.00 sec)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzsjxy.html