InnoDB中的锁

InnoDB 中的锁

本文翻译自mysql5.6官方文档:InnoDB Lockinghtml

本文介绍InnoDB包含的锁的种类:mysql

  • 共享锁(Shared Lock)和 排他锁(Exclusive Lock)
  • 意向锁(Intention Locks)
  • Record Locks
  • Gap Locks 间隙锁
  • Next-Key Locks
  • Insert Intention Locks
  • AUTO-INC Locks

共享锁和排他锁

InnoDB实现了两种类型的行级锁:shared(S) locks、exclusive(X) locks。算法

  • 共享锁(S):容许事务读取一行数据
  • 排他锁(X):容许事务更新或删除一行数据

若是事务T1在记录r上持有共享锁,那么其余事务T2对记录r的锁请求处理方式以下:sql

  • 事务T2能够当即得到请求的S锁。结果是T1和T2在记录r上都得到了S锁。
  • 事务T2不能当即得到请求的X锁。

若是事务T1在行r上得到了X锁,事务T2在行r上请求的任何一种类型的锁都不能当即得到。反而,事务T2不得不等待T1释放在行r上的X锁。并发

意向锁(Intention Locks)

InnoDB支持多粒度(multiple granularity)锁定,这种锁定容许行级锁和表级锁共存。为了在实际中支持多粒度加锁操做,InnoDB使用了一种附加类型的锁,称为意向锁。在InnoDB中意向锁是表级锁。spa

InnoDB使用了两种类型的意向锁(假设事务T已请求表t上指定类型的锁):翻译

  • 意向共享锁(IS): 事务T打算得到表t上某几行的共享锁。
  • 意向排他锁(IX): 事务T打算得到表t上某几行的排他锁。

例如,SELECT ... LOCK IN SHARE MODE会请求IS锁,SELECT ... FOR UPDATE会请求IX锁。code

意向锁的协议以下:orm

  • 一个事务在表的某一行上得到S锁以前,它必须先在这个表上得到IS锁或者更强的锁。
  • 一个事务在表的某一行上得到X所以前,它必须先在这个表上得到IX锁。

这些规则能够总结以下:htm

X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

若是事务请求的锁跟已经存在的锁兼容,则事务会获得它请求的锁,若跟已存在的锁有冲突,则不会得到它请求的锁。事务会一直等待直到存在冲突的锁被释放。若是一个锁请求跟已存在的锁有冲突而且不能得到,则会产生死锁。

Record Locks

Record Lock是一个在索引记录上的锁。例如,SELECT c1 FOR UPDATE FROM t WHERE c1 = 10; 防止其余事务在行t.c1 = 10上的插入、删除和更新操做。

Record locks老是锁定索引记录,即便表中没有一个索引。这种状况下,InnoDB会使用隐式的聚簇索引来进行锁定。

间隙锁(Gap Locks)

间隙锁是在索引记录之间的间隙上的锁,或者是在第一个索引记录以前或最后一个索引记录以后的间隙上的锁。例如,SELECT c1 FOR UPDATE FROM t WHERE c1 BETWEEN 10 and 20; 能够阻止其余事务插入t.c1 = 15的记录,不论列中是否已有此值,由于在此范围内的全部现有值之间的间隙被锁定了。

间隙可能会跨越单个索引值,多个索引值,甚至空值。

当使用惟一索引查询惟一行时不须要使用间隙锁。(这不包含此种状况,查询条件只包含多列惟一索引的一部分字段;这种状况下仍是会用到间隙锁。)例如,若是id列上有惟一索引,下面的语句仅对id等于100的行使用索引记录锁,不会关心其余会话是否在间隙以前插入行:

SELECT * FROM child WHERE id = 100;

若id字段未加索引或者为非惟一索引,此语句会锁住id=100记录前面的间隙。

值得注意的是经过不一样的事务相互冲突的锁能够持有同一个间隙。例如,事务A在一个间隙上持有共享间隙锁(gap S-lock),同时事务B能够在此间隙上持有排他间隙锁(gap X-lock)。这种状况被容许的缘由是:若是一个记录从索引上被清除,则此记录上被不一样事务持有的间隙锁必须合并。

在InnoDB中,间隙锁是“彻底被抑制(purely inhibitive)”的,也就是说它只会阻止其余事务在间隙中进行插入操做。它不会阻止不一样的事务在同一间隙上获取间隙锁。所以,排他间隙锁跟共享间隙锁具备相同的效果。

当事务隔离级别设置为 READ COMMITTED 或者 启用系统变量innodb_locks_unsafe_for_binlog时,间隙锁能够被显示禁用。在这种状况下,间隙锁在查询和索引扫描时会被禁用,仅在外键约束检查和惟一性检查时启用。

当事务隔离级别设置为READ COMMITTED或者启用系统变量innodb_locks_unsafe_for_binlog时还有其余效果。MySQL在评估完WHERE条件后针对不匹配的行会释放记录锁。对于UPDATE语句,InnoDB作了半一致性(semi-consistent)读,它会返回最后提交的版本到MySQL,以便MySQL能够决定这行是否跟UPDATE语句的WHERE条件匹配。

Next-Key Locks

Next-Key Lock是索引记录上的record lock与索引记录以前间隙上的gap lock的组合。

当InnoDB搜索或扫描表的索引时会使用行级锁,会在它遇到的索引记录上设置共享锁或排他锁。所以,行级锁就是索引记录锁。索引记录上的next-key lock也会对此索引记录以前的间隙产生影响。也就是说,next-key lock是索引记录锁加上该索引记录以前间隙上的间隙锁。若是一个会话在索引的记录R上持有一个共享锁或排他锁,那么另外一个会话不能在R以前的间隙上当即插入一个新的索引记录。

假设一个索引包含10,11,13和20这四个值,那么该索引的next-key locks可能覆盖以下区间:

(负无穷, 10]
(10, 11]
(11, 13]
(13, 20]
(20, 正无穷)

对于最后一个区间,next-key lock锁住的是索引中大于最大值的间隙(锁住的仅仅是索引中最大值以后的间隙)。上确界是一个大于索引中任何一个记录的伪记录。上确界并非一个真实的索引记录,所以,事实上next-key lock仅仅锁住的是最大索引值以后的间隙。

默认状况下,InnoDB运行在REPEATABLE READ事务隔离级别而且系统变量innodb_locks_unsafe_for_binlog 未启用。在这种状况下,InnoDB使用next-key lock搜索和扫描索引,避免了幻影行(phantom rows)

Insert Intention Locks

插入意向锁是在插入行以前经过INSERT操做设置的一种间隙锁。这个锁发出以以下方式插入的信号:若是多个事务在同一索引间隙上的不一样位置进行插入时,他们并不须要彼此相互等待。假设有值为4和7的索引记录,两个事务尝试分别插入5和6,在得到要插入行上的排他锁以前,每一个都用插入意向锁锁住4和7之间的间隙,可是不会相互阻塞,由于插入的行不存在冲突。

下面的例子演示了事务在得到插入记录上的排他锁以前先得到插入意向锁。这个例子包含两个客户端,A和B。

客户端A建立了一个包含两个索引记录(90和102)的表,而后开启了事务,在id值大于100的索引记录上设置了排他锁。这个排他锁包含记录102以前的间隙锁:

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;
+-----+
| id  |
+-----+
| 102 |
+-----+

客户端B开启事务后,往这个间隙中插入记录。这个事务持有插入意向锁,并等待得到排他锁。

mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);

经过运行SHOW ENGINE INNODB STATUS来查看插入意向锁的信息,TRANSACTIONS标题下的信息以下:

mysql> SHOW ENGINE INNODB STATUS\G  
...
SHOW ENGINE INNODB STATUS
---TRANSACTION 8731, ACTIVE 7 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 3, OS thread handle 0x7f996beac700, query id 30 localhost root update
INSERT INTO child (id) VALUES (101)
------- TRX HAS BEEN WAITING 7 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child` 
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000066; asc    f;;
 1: len 6; hex 000000002215; asc     " ;;
2: len 7; hex 9000000172011c; asc     r  ;;...

AUTO-INC Locks

自增加锁是一个特殊的表级锁,事务用此锁为AUTO_INCREMENT字段插入数据。最简单的状况下,若是一个事务正在往表中插入数据,那么另外一个事务的插入操做必须等待,以致于第一个事务的插入操做可以得到连续的主键值。

innodb_autoinc_lock_mode配置选项可以控制自增加锁的算法。它可让你在自增加值的可预测序列和最大并发插入操做之间作权衡。更多信息查看: AUTO_INCREMENT Handling in InnoDB

具体分析加锁例子可查看何成登的博文:MySQL 加锁处理分析

参考:

相关文章
相关标签/搜索