共享锁和排他锁
共享锁(S)允许持有锁的事务读取行。
排他锁(x)锁允许持有锁的事务更新或删除行
如果事务t1持有共享锁,则来自某些不同事务T2对行锁定的请求将按以下方式处理:
如果T2请求用于S锁可以立即被授予。结果是,T1与T2同时持有S锁。
如果T2请求一个X锁则不能立即授予。
如果某个事务T1在行上具有排他锁,则不能立即批准某个不同事务T2对任一类型的锁的请求。相反,事务T2必须等待事务T1释放对行的锁定
意向锁
InnoDB支持多种粒度锁定,允许行锁和表锁并存。意向锁是表级锁,指示事务稍后对表中的行需要哪种类型的锁(共享锁或排他锁)。有两种类型的意图锁:
意图共享锁(IS)指示一个事务打算在表中设置各个行的共享锁。
意图独占锁(IX)指示一个事务打算在表中设置各个行的排他锁。
意向锁定协议
在事务可以获取表中某行上的共享锁之前,它必须首先获取该表上的IS锁或更高级别的锁。
在事务可以获取表中某行的排它锁之前,它必须首先获取该表中的锁IX。
如果一个锁与现有锁兼容,则将其授予给请求的事务,但如果与现有锁冲突,则不授予该锁。事务等待直到冲突的现有锁被释放。如果锁定请求与现有锁定发生冲突,并且由于无法被授予许可而导致死锁,则会发生错误。
记录锁
记录锁是对索引记录的锁定。例如, SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE; 可以防止任何其他事务插入、更新或删除t.c1值为10的行
记录锁始终锁定索引记录,即使没有定义索引的表也是如此。在这种情况下,InnoDB将创建一个隐藏的聚集索引,并将该索引用于记录锁定
间隙锁
间隙锁是对索引记录之间的间隙的锁定,或者是对第一个或最后一个索引记录之前的间隙的锁定。例如,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;其中c1在10到20之间进行更新;防止其他事务向列t.c1插入15的值,无论列中是否已经存在这样的值,因为范围中所有现有值之间的间隙都被锁定了。
间隙可能跨越单个索引值,多个索引值,甚至为空
间隙锁是性能和并发性之间权衡的一部分,在某些事务隔离级别中使用,而在其他级别中不使用
对于使用唯一索引来锁定唯一行来锁定行的语句,不需要间隙锁定。(这不包括搜索条件只包含多列唯一索引的一些列的情况;在这种情况下,会发生间隙锁)例如,如果该id列具有唯一索引,则以下语句仅使用一个具有id值100的行的索引记录锁定,其他会话是否在前面的间隙中插入行并不重要:
SELECT * FROM child WHERE id = 100;
如果id未索引或索引不唯一,则该语句会锁定前面的间隙。
在这里还值得注意的是,可以通过不同的事务将冲突的锁保持在间隙上。例如,事务A可以在间隙上保留一个共享的间隙锁(间隙S锁),而事务B可以在同一间隙上保留排他的间隙锁(间隙X锁)。允许冲突的间隙锁的原因是,如果从索引中清除记录,则必须合并由不同事务保留在记录上的间隙锁。
间隙锁定InnoDB是“纯粹抑制性的”,这意味着它们的唯一目的是防止其他事务插入间隙。间隙锁可以共存。一个事务进行的间隙锁定不会阻止另一事务对相同的间隙进行间隙锁定。共享和专用间隙锁之间没有区别。它们彼此不冲突,并且执行相同的功能。
下一键锁
下一键锁定是索引记录上的记录锁定和索引记录之前的间隙上的间隙锁定的组合。
InnoDB执行行级锁定,以使其在搜索或扫描表索引时对遇到的索引记录设置共享或排他锁。因此,行级锁实际上是索引记录锁。索引记录上的下一键锁定也会影响该索引记录之前的“间隙”。即,下一键锁定是索引记录锁定加上索引记录之前的间隙上的间隙锁定。
如果一个会话R在索引中的记录上具有共享或排他锁 ,则另一会话不能在R索引顺序之前的间隙中插入新的索引记录。
假定索引包含值10、11、13和20。此索引的可能的下一键锁定涵盖以下间隔,其中,圆括号表示排除区间端点,方括号表示包括端点:
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
对于最后一个间隔,下键锁锁定在上面的索引的最大值以上的间隙,并且“supremum”伪记录的值比索引中任何实际值都高。上界不是一个真正的索引记录,因此,实际上,这个下一键锁只锁定最大索引值后面的间隙。
插入意图锁
插入意图锁定是一种通过INSERT行插入之前的操作设置的间隙锁定。
此锁以这种方式发出信号,表明要插入的意图是:如果多个事务未插入间隙中的相同位置,则不必等待彼此插入的多个事务。假设有索引记录,其值分别为4和7。单独的事务分别尝试插入值5和6,在获得插入行的排他锁之前,每个事务都使用插入意图锁来锁定4和7之间的间隙,但不要互相阻塞,因为行是无冲突的。
死锁
查看InnoDB用户事务中的最后一个死锁
SHOW ENGINE INNODB STATUS
死锁是指由于每个事务都持有对方需要的锁而无法进行其他事务的情况。因为这两个事务都在等待资源变得可用,所以两个都不会释放它持有的锁。
死锁的可能性不受隔离级别的影响,因为隔离级别更改了读取操作的行为,而死锁则由于写入操作而发生
如果频繁出现死锁,启用innodb_print_all_deadlocks可将有关所有死锁的信息打印到mysqld错误日志中。
在高并发系统上,当多个线程等待相同的锁时,死锁检测(默认启用)会导致速度变慢。有时,禁用死锁检测并在发生死锁时依靠innodb_lock_wait_timeout设置事务回滚可能会更有效。可以使用innodb_deadlock_detect配置选项禁用死锁检测。
为了减少死锁的可能性,请使用事务而不是LOCK TABLES语句;保持用于插入或更新数据的事务足够小,以使其不长时间保持打开状态
示例
当多个事务锁定表中的行但顺序相反时,可能会发生死锁
客户端A创建一个表,然后开始事务。在事务中,A通过在共享模式下选择行来获得对行的S锁定
mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
Query OK, 0 rows affected (1.07 sec)
mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.09 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;
+------+
| i |
+------+
| 1 |
+------+
接下来客户端B开始事务并尝试从表中删除该行
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> DELETE FROM t WHERE i = 1;
删除操作需要一个X锁。由于客户端A持有的锁与S锁不兼容,所以不能授予锁,因此该请求进入针对行和客户端B块的锁请求队列中
最后,客户端A还尝试从表中删除该行
mysql> DELETE FROM t WHERE i = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction
因为客户端A需要X锁才能删除该行。但是,不能授予请求该锁定,因为客户端B已经有一个X锁定请求,并且正在等待客户端A释放其S锁定。由于B事先要求锁,所以A持有的S锁也不能升级为X锁。结果, InnoDB为其中一个客户端生成错误并释放其锁。客户端返回此错误:
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction