共享锁(S锁, Share Lock)和独占锁(X锁, Exclusive Locks)

行级锁

InnoDB实现了行级锁,分别有S锁和X锁

  • 事务持有某行的S锁就可以读取该行
  • 事务持有某行的X锁就可以删改该行 若事务T1持有某行r的S锁, 然后另一事务T2请求行r的锁, 具体处理如下:
  • T2请求S锁, T2会立刻被授予S锁
  • T2请求X锁, T2会阻塞, 直到行r上的S锁和X锁全部被释放

表级锁

表级S锁: LOCK TABLES ... READ 等DML语句. 表级X锁: LOCK TABLES ... WRITE 等DML语句.

意向锁(Intention Lock)

InnoDB支持多重粒度锁, 行锁(row-level lock)/表锁(table-level lock)/页锁(不做讨论), 行锁和表锁是可以共存的. 例如LOCK TABLES ... WRITE 请求某个表的一个表级X锁. 为了多重粒度锁的可行, InnoDB使用意向锁(Intention Locks). 意向锁是一种为了标记事务请求行锁类型(X锁或S锁)的表级锁.

  • 意向共享锁(IS)表示事务将对表中某些行设置行级S锁.
  • 意向独占锁(IX)表示事务将对表中某些行设置行级X锁.

例如, SELECT ... FOR SHARE 设置一个IS锁, SELECT ... FOR UPDATE 设置一个IX锁.

意向锁定协议如下:

  • 在事务请求行级S锁之前, 必须先请求表级IS锁或者更强的锁.
  • 在事务请求行级X锁之前, 必须先请求表级IX锁.

表级锁兼容矩阵如下:

X IX S IS
X × × × ×
IX × ×
S × ×
IS ×

特别注明: 以上的S和X都是表级的S锁和表级的X锁,与行锁无关

若事务请求的表级锁与已存在的表级锁兼容, 则会立刻被授予, 若冲突事务可能会等待或者抛出死锁错误.

意向锁不会阻塞除了LOCK TABLES ... WRITE之类的表级锁的任意请求语句.

意向锁的主要目的是标记有事务正在或者即将锁定表中的行.

行锁 (Record Lock)

行锁是一种索引记录锁. 例如, SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE, 可以防止其他事务对t.c1 = 10的所有记录行进行删改.

行锁只会锁住索引记录, 即使您的表没有定义索引, InnoDB必然存在一个显式或隐式主键索引. 具体请看:“Clustered and Secondary Indexes”

事务数据和行锁可以查看SHOW ENGINE INNODB STATUS的信息

MySQL8.0可以参考以下3个表

  • performance_schema.events_transactions_current: 事务信息
  • performance_schema.data_lock_waits: 阻塞锁列表
  • performance_schema.data_locks: 表中行锁/表锁列表

间隙锁(Gap Locks)

间隙锁作用于索引记录区间, 或小与索引的第一条记录/大于索引的最后一条记录.

间隙锁的唯一作用是防止其他事务插入数据到间隙中, 以免导致幻行问题.

间隙锁不区分X锁还是S锁, 他们都与插入意向锁不兼容.

同一个间隙锁可以允许同时存在X锁和S锁, 因为如果从索引中清除记录,则必须合并由不同事务保留在记录上的间隙锁。

间隙锁可以被显示禁用, 将隔离级别改为RC就会禁用间隙锁. 此时间隙锁仅仅用于检查外键约束和检查重复键.

RC隔离级别还会有以下影响:

  • MySQL在评估WHERE条件之后释放不匹配行的记录锁.
  • 对于UPDATE语句, InnoDB进行"半一致读(semi-consistent read)", InnoDB会将最新提交的事务版本返回给MySQL, 以便MySQL用于匹配UPDATE的WHERE子句.

Next-key Lock

Next-Key Lock是由当前记录锁和当前记录之前的间隙组合而成的锁.

表t的索引c可能存在的Next-Key Lock为: (-∞, 1],(1, 4],(4, 7],(7, 10],(10, 15],(15, +∞]

InnoDB默认隔离级别RR下, Next-Key Lock是为了防止幻行(Phantom Rows)问题.

例子:

代码

DROP TABLE IF EXISTS `t`;

CREATE TABLE `t` (

  `a` int(11NOT NULL,

  `b` int(11NOT NULL,

  `c` int(10) unsigned NOT NULL DEFAULT '0',

  PRIMARY KEY (`a`USING BTREE,

  KEY `c` (`c`) USING BTREE

);

INSERT INTO `t` VALUES (111);

INSERT INTO `t` VALUES (444);

INSERT INTO `t` VALUES (777);

INSERT INTO `t` VALUES (101010);

INSERT INTO `t` VALUES (151515);

时间 会话A 会话B
1 BEGIN
2 select c from t where c = 7 for update;
3 lock range[c]:(4, 7],(7,10) BEGIN
4 select c from t where c = 4 for update;
5 COMMIT lock range[c]:(1,4],(4,7)

GapLock

插入意向锁(Insert Intention Lock)

插入意向锁相互兼容, 但是与其他间隙锁不兼容.

假设存在索引记录4 和 7, 此时欲插入5 和 6, 他们都会锁住(4, 7), 但是5 和 6不冲突, 所以不是阻塞.