InnoDB实现了行级锁,分别有S锁和X锁
表级S锁: LOCK TABLES ... READ
等DML语句. 表级X锁: LOCK TABLES ... WRITE
等DML语句.
InnoDB支持多重粒度锁, 行锁(row-level lock)/表锁(table-level lock)/页锁(不做讨论), 行锁和表锁是可以共存的. 例如LOCK TABLES ... WRITE
请求某个表的一个表级X锁. 为了多重粒度锁的可行, InnoDB使用意向锁(Intention Locks). 意向锁是一种为了标记事务请求行锁类型(X锁或S锁)的表级锁.
例如, SELECT ... FOR SHARE
设置一个IS锁, SELECT ... FOR UPDATE
设置一个IX锁.
意向锁定协议如下:
表级锁兼容矩阵如下:
X | IX | S | IS | |
---|---|---|---|---|
X | × | × | × | × |
IX | × | √ | × | √ |
S | × | × | √ | √ |
IS | × | √ | √ | √ |
特别注明: 以上的S和X都是表级的S锁和表级的X锁,与行锁无关
若事务请求的表级锁与已存在的表级锁兼容, 则会立刻被授予, 若冲突事务可能会等待或者抛出死锁错误.
意向锁不会阻塞除了LOCK TABLES ... WRITE
之类的表级锁的任意请求语句.
意向锁的主要目的是标记有事务正在或者即将锁定表中的行.
行锁是一种索引记录锁. 例如, 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
: 表中行锁/表锁列表间隙锁作用于索引记录区间, 或小与索引的第一条记录/大于索引的最后一条记录.
间隙锁的唯一作用是防止其他事务插入数据到间隙中, 以免导致幻行问题.
间隙锁不区分X锁还是S锁, 他们都与插入意向锁不兼容.
同一个间隙锁可以允许同时存在X锁和S锁, 因为如果从索引中清除记录,则必须合并由不同事务保留在记录上的间隙锁。
间隙锁可以被显示禁用, 将隔离级别改为RC就会禁用间隙锁. 此时间隙锁仅仅用于检查外键约束和检查重复键.
RC隔离级别还会有以下影响:
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(11) NOT NULL,
`b` int(11) NOT NULL,
`c` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`a`) USING BTREE,
KEY `c` (`c`) USING BTREE
);
INSERT INTO `t` VALUES (1, 1, 1);
INSERT INTO `t` VALUES (4, 4, 4);
INSERT INTO `t` VALUES (7, 7, 7);
INSERT INTO `t` VALUES (10, 10, 10);
INSERT INTO `t` VALUES (15, 15, 15);
时间 | 会话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) |
插入意向锁相互兼容, 但是与其他间隙锁不兼容.
假设存在索引记录4 和 7, 此时欲插入5 和 6, 他们都会锁住(4, 7), 但是5 和 6不冲突, 所以不是阻塞.