MySQL InnoDB锁机制(二)

上一篇文章我们提到MySQL InnoDB对数据行的锁定类型一共有四种:共享锁(读锁,S锁)、排他锁(写锁,X锁)、意向共享锁(IS锁)和意向排他锁(IX锁),今天我们要讨论的是MySQL InnoDB对数据行的锁定方式。

MySQL InnoDB支持三种行锁定方式:

  • 行锁(Record Lock):锁直接加在索引记录上面。
  • 间隙锁(Gap Lock):锁加在不存在的空闲空间,可以是两个索引记录之间,也可能是第一个索引记录之前或最后一个索引之后的空间。
  • Next-Key Lock:行锁与间隙锁组合起来用就叫做Next-Key Lock。

默认情况下,InnoDB工作在可重复读隔离级别下,并且以Next-Key Lock的方式对数据行进行加锁,这样可以有效防止幻读的发生。Next-Key Lock是行锁与间隙锁的组合,这样,当InnoDB扫描索引记录的时候,会首先对选中的索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)如果一个间隙被事务T1加了锁,其它事务是不能在这个间隙插入记录的

我们来看看例子,首先建一张表。

CREATE TABLE tb1 (
  id int(11) NOT NULL,
  id2 int(11) NOT NULL,
  PRIMARY KEY (id),
  KEY idx (id2)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

创建一些记录。

insert into tb1 values(1, 3), (2, 6), (3, 9);

 tb1表现在有3条记录,其中普通索引字段id2的值3、6、9把间隙分成了四份:(-,3)、(3、6)、(6、9)、(9、+)。现在我们看看基于id2 = 6加锁的情况,会话S1中对id2 = 6的记录加S锁。

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from tb1 where id2 = 6 lock in share mode;
+----+-----+
| id | id2 |
+----+-----+
|  2 |   6 |
+----+-----+
1 row in set (0.00 sec)

mysql>

会话S2中尝试插入id2 = 5或id2 = 7的记录。

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into tb1 values(4, 5);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tb1 values(4, 7);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql>

从结果可知,会话2发生了锁等待超时,因为会话S1中的事务锁住了这些空隙(3、6)与(6、9)。如果插入的记录是id2 = 1或id2 = 10,那就不会有问题,因为这些间隙没有被任何事务锁住,如:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into tb1 values(4, 5);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tb1 values(4, 7);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tb1 values(4, 2);
Query OK, 1 row affected (0.00 sec)

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

mysql>

间隙锁在InnoDB的唯一作用就是防止其它事务的插入操作,以此来达到防止幻读的发生,所以间隙锁不分什么共享锁与排它锁。另外,在上面的例子中,我们选择的是一个普通(非唯一)索引字段来测试的,这不是随便选的,因为如果InnoDB扫描的是一个主键、或是一个唯一索引的话,那InnoDB只会采用行锁方式来加锁,而不会使用Next-Key Lock的方式,也就是说不会对索引之间的间隙加锁,仔细想想的话,这个并不难理解,大家也可以自己测试一下。

要禁止间隙锁的话,可以把隔离级别降为读已提交,或者开启参数innodb_locks_unsafe_for_binlog

猜你喜欢

转载自ouyanggod.iteye.com/blog/2166215