乐趣区

关于java:InnoDB学习五之数据库锁

InnoDB 存储引擎的默认隔离级别事可反复读,MVCC 多版本并发管制仅仅解决了快照读状况下的数据隔离,而对于以后读,InnoDB 通过锁来进行并发管制。

InnoDB 锁

本文次要参考了 MySQL 官网文档,并在下面增加了一些本人的了解,有趣味看英文的也能够看 MySQL 官网文档。本文分为以下章节:

  1. 共享锁和独占锁;
  2. 意向锁;
  3. 行锁;
  4. 间隙锁;
  5. Next-Key 锁
  6. 插入意向锁;
  7. 自增锁;

共享锁和排他锁

InnoDB 锁的最小粒度是行锁,行锁能够分为两大类:共享锁(S)和独占锁(X)。

  • 共享锁:持有某行数据共享锁的事务,能够读取行锁对应行的数据;
  • 独占锁:持有某行数据独占锁的事务,能够批改行锁对应行的数据;

如果事务 T1 持有行 R 的共享锁,那么对于事务 T2 对行 R 的拜访分为两种状况:

  • 如果事务 T2 申请行 R 的共享锁,则事务 T2 能够申请胜利,申请实现后事务 T1 和事务 T2 同时持有行 R 的共享锁;
  • 如果事务 T2 申请行 R 的排他锁,事务 T2 会被阻塞,直到事务 T1 开释锁或者事务超时回滚;

如果事务 T1 持有行 R 的共享锁,那么不论事务 T2 申请 R 行的共享锁还是排他锁,都会被事务 T1 阻塞,直到事务 T1 开释锁或事务 T2 回滚。

意向锁

InnoDB 反对反对多种粒度的锁,比方对于以下两个 SQL 语句,加锁的对象就齐全不同:

  1. SELECT * FROM USER_INFO WHERE ID = 1 FOR UPDATE,其中 ID 是主键,ID= 1 的数据行存在,那么这句 SQL 会获取 ID= 1 的数据行的独占锁;
  2. LOCK TABLES USER_INFO WRITE,其中 USER_INFO 表存在,那么这句 SQL 会获取 USER_INFO 表的独占锁;

表锁和行锁之间也存在互斥的状况,比方表上的独占锁和表中每一行数据的独占锁之间抵触(锁表了当然不容许批改表中的内容),这种互斥要怎么实现呢?InnoDB 应用了意向锁实现表锁和行锁之间的互斥,意向锁是表级别的锁,对一行数据增加独占锁或排他锁时,会先向数据行所在的表增加意向锁,意向锁分为两种类型:

  1. 共享意向锁:事务会对表中的某一行数据增加共享锁;
  2. 排他意向锁:事务会对表中的某一行数据增加排他锁;

所以对表增加意向锁的状况也分两种:

  1. 如果事务须要获取某一行数据的共享锁,那么必然会首先获取数据所在表的共享意向锁,如 SQL 语句 SELECT * FROM USER_INFO WHERE ID = 1 LOCK IN SHARE MODE 会首先向表 USER_INFO 增加共享意向锁;
  2. 如果事务须要获取某一行数据的排他锁,那么必然会首先获取数据所在表的排他意向锁,如 SQL 语句 SELECT * FROM USER_INFO WHERE ID = 1 FOR UPDATE 会首先向表 USER_INFO 增加共享排他锁;

表锁和意向锁之间的抵触状况如下所示:

表排他锁 共享排他锁 表共享锁 共享意向锁
表排他锁 抵触 抵触 抵触 抵触
共享排他锁 抵触 不抵触 抵触 不抵触
表共享锁 抵触 抵触 不抵触 不抵触
共享意向锁 抵触 不抵触 不抵触 不抵触

如果事务申请的表锁和体现有的锁之间不抵触,那么事务能够申请锁胜利;如果事务申请的锁和体现有的锁抵触,那么事务必须期待表锁被开释,或者以后事务须要回滚。

咱们能够留神到,意向锁之间不会互斥,因为意向锁代表的是批改表中的某一行数据,两个意向锁示意批改表中的两行数据,所以两个意向锁不肯定会抵触。用意锁只会和表锁之间抵触,如 LOCK TABLES USER_INFO WRITE 会向表增加表锁。

InnoDB 中,咱们能够通过 SHOW ENGINE INNODB STATUS 语句查看表锁情况,以下为锁情况示例:

TABLE LOCK table `test`.`t` trx id 10080 lock mode IX

行锁

行锁是增加在索引上的锁,例如对于SELECT * FROM USER_INFO WHERE ID = 1 FOR UPDATE,在 ID 是惟一索引的状况下,该 SQL 语句会对 ID 对应的索引节点上增加排他锁,阻止其它事务批改该行数据。行锁增加的对象是索引节点,如果表没有定义索引,InnoDB 会创立一个暗藏的汇集索引,并应用该索引来增加行锁。

InnoDB 中,咱们能够通过 SHOW ENGINE INNODB STATUS 语句查看行锁情况,以下为锁情况示例:

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 00000000274f; asc     'O;;
 2: len 7; hex b60000019d0110; asc        ;;

间隙锁

间隙锁加锁的对象是索引之间的间隙,例如如果对于 SQL 语句SELECT * FROM USER_INFO WHERE ID>10 and ID<20 FOR UPDATE,该事务会向数据库中 ID 索引树上 10~20 之间的所有节点间隙增加间隙锁。当另一个事务尝试向数据库中插入 ID=15 的记录时,会被间隙锁阻塞。

间隙锁的间隙中能够蕴含多个索引节点、单个索引阶段或者不蕴含任何节点。间隙锁次要用于解决可反复读隔离级别下的幻读问题。

对于惟一索引,如果应用等值查问,那么间隙锁会进化为行锁,如下 SQL 中,ID 是惟一索引列,并且 ID=100 的数据存在,那么以下 SQL 只会增加行锁:

SELECT * FROM child WHERE id = 100;

如果 ID 不是惟一索引,那么上文中的 SQL 语句则会给 ID 索引树中的 Id=100 和前一个节点之间的间隙增加 GAP 锁,间隙锁之间不抵触,并且两个间隙锁之间的节点被删除之后,两个间隙锁还会合并为一个间隙锁。

InnoDB 中的间隙锁只有一个目标,阻止向间隙内插入数据,间隙锁只和插入意向锁抵触,和其它任何锁都不抵触。能够通过将事务隔离级别更改为读已提交或启用 innodb_locks_unsafe_for_binlog 零碎变量来禁用间隙锁。

在禁用间隙锁的状况下,InnoDB 还会将开释不匹配行的记录锁(违反了加锁的 2PL 准则)。对于 UPDATE 语句,InnoDB 执行 ” 半统一 ” 读取:读取最新提交的数据,MySQL 应用最新提交的数据判断是否合乎 UPDATE 语句中的 WHERE 条件。

Next-Key 锁

Next-Key 锁是行锁和间隙锁的组合,在 InnoDB 惟一索引加锁的过程中,InnoDB 会从索引中查找符合条件的索引节点,并对这些符合条件的索引节点增加行锁。

如果对某行记录加 Next-Key 锁而不是行锁,那么而 Next-Key 锁不仅会对记录自身增加行锁,还会对行锁之前的间隙增加间隙锁,二者组合成了 Next-Key。Next-Key 不容许其它事务向加锁的间隙中插入数据。

假如 ID 索引蕴含值 10、11、13 和 20,那么先索引的节点增加 Next-Key 锁可能会有以下几种状况,下文中圆括号示意排除间隙,方括号示意蕴含端点:

  1. 如果对索引 10 所在的节点加 Next-Key,加锁范畴为(负无穷, 10];
  2. 如果对索引 11 所在的节点加 Next-Key,加锁范畴为(10, 11];
  3. 如果对索引 13 所在的节点加 Next-Key,加锁范畴为(11, 13];
  4. 如果对索引 20 所在的节点加 Next-Key,加锁范畴为(13, 20];
  5. 如果 20 之后的间隙加 Next-Key,加锁范畴为(20, 正无穷);

对于最初一个间隙,能够了解为:InnoDB 中有一个虚构的最大节点,会在该节点上增加 Next-Key.

InnoDB 中,咱们能够通过 SHOW ENGINE INNODB STATUS 语句查看 Next-Key 锁情况,以下为锁情况示例:

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 00000000274f; asc     'O;;
 2: len 7; hex b60000019d0110; asc        ;;

插入意向锁

插入意向锁是向数据库中插入一行新数据时,须要向插入间隙增加的一种间隙锁。插入意向锁之间不抵触,例如两个事务别离打算向 (4,7] 之间的间隙插入 5 和 6,这两个事务都会向 (4,7] 中的间隙增加插入意向锁,然而二者互不阻塞。

假如有两个事务,事务 A 和事务 B,数据库表中蕴含两条记录 90102。事务 A 对 ID 大于 100 的索引记录增加 Next-Key 独占锁定:

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 尝试向数据库中插入一条 101 的记录:

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

通过SHOW ENGINE INNODB STATUS,咱们能够看到此时数据库的锁期待状况:

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_INCREAMENT 的表中的事务应用。在最简略的状况下,如果一个事务正在向表中插入数据行,该事务会占有自增所,其它任何事务在向表中插入数据时都会被该锁阻塞。咱们能够通过 innodb_autoinc_lock_mode 变量管制自增锁的自增的算法,MySQL 对自增锁有很多优化,本文不具体介绍。

我是御狐神,欢送大家关注我的微信公众号:wzm2zsd

参考文档

MySQL 官网文档

本文最先公布至微信公众号,版权所有,禁止转载!

退出移动版