本文描述InnoDB引擎下的锁类型
1、共享锁和排他锁(Shared and Exclusive Locks,又称S和X锁)
持有共享锁的事务允许读数据,持有独占锁的事务,可以修改或删除数据。
如果一个事务T1持有了S锁,其他事务T2也可以获取S锁,但是必须等待T1释放S锁,才能获取X锁
2、意向锁(Intention Locks)
意向锁分为意向共享(IS)锁和意向排他(IX)锁。
意向锁是表级锁,一个事务在获得S锁时,必须先获得IS或更高级别的锁,一个事物在获得X锁时,必须先获得IX锁
意向锁兼容矩阵如下:
X | IX | S | IS | |
---|---|---|---|---|
X | Conflict | Conflict | Conflict | Conflict |
IX | Conflict | Compatible | Conflict | Compatible |
S | Conflict | Conflict | Compatible | Compatible |
IS | Conflict | Compatible | Compatible | Compatible |
意向锁作用:提高并发性能,允许MySQL在加表级锁时不必检查每一行的锁状态,从而减少锁冲突。
3、记录锁(Record Locks)
记录锁锁的是索引记录,即使一张表没有定义索引,InnoDB会创建一个隐藏的聚集索引并使用该聚集索引来锁记录,
例如:
SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
将阻止其他事务插入、修改或删除c1=10的记录
4、间隙锁(Gap Locks)
间隙锁锁的是索引之间的间隙,非唯一索引才需要Gap Locks,如果是唯一索引已经确保数据的唯一性,不需要Gap Locks,这个时候只有Record Locks。
举个例子,如果c1是一个唯一索引,下面的SQL语句只会锁c1=10这条记录,而不关心记录之间的间隙
SELECT c1 FROM t WHERE c1 = 10
如果c1不是唯一索引,上面语句会锁间隙,而不仅仅是c1=10的记录间隙锁的目的是只是阻止其他事务往这个间隙插入数据,间隙锁没有共享和独占之说,两个不同的事物可以同时获取相同间隙的间隙锁,这个时候任意一个事务往间隙插入数据都会阻塞。只要没有插入操作,就不会被阻塞。
5、Next-Key Locks
next-key lock是record lock和gap lock的组合。间隙锁是开区间的锁,next-key lock包含了record lock,锁的范围是前开后闭区间。
《MySQL实战45讲》总结道:
我总结的加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug”。
- 原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。
- 原则 2:查找过程中访问到的对象才会加锁。
- 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
- 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
- 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
如何理解索引间隙?
举个例子,按下面脚本构建测试表
create table t (id int primary key , c1 int, key idx_a(c1));
INSERT INTO t (id, c1) VALUES(1, 5);
INSERT INTO t (id, c1) VALUES(7, 7);
INSERT INTO t (id, c1) VALUES(10, 11);
INSERT INTO t (id, c1) VALUES(11, 11);
INSERT INTO t (id, c1) VALUES(13, 13);
INSERT INTO t (id, c1) VALUES(20, 20);
在T1上执行select * from t where c1=11 for update 不提交
在T2上执行
INSERT INTO t (id, c1) VALUES(8, 7); -- 执行被阻塞
INSERT INTO t (id, c1) VALUES(6, 7); -- 执行成功
为什么同样是c1=11,第一条插入阻塞,第二条插入成功呢?按照前面描述的前开后闭原则,两条应该都要插入成功才对。这就要正确理解T1 next-key lock锁范围。
下面通过一张图体现:
根据前面非唯一索引等值查询的锁规则结合上图可知,锁的范围是
(7,7)-(13,13)开区间内,任何在这范围内插入数据都会被阻塞。这就解释了,为什么同样是c1=7,id为8的不能插入,id为6的可以插入。
另外可以通过查询 performance_schema.data_locks得到辅证
可以看到,首先获取了一个意向锁,同时获取了两行c1=11的记录锁(11,10)(11,11),然后获取两个主键的锁10,11 最后获取间隙锁(13,13)。
根据上面分析也可以推导出下面执行的结果
INSERT INTO t (id, c1) VALUES(12, 13); – 执行会被阻塞
INSERT INTO t (id, c1) VALUES(14, 13); – 可以成功插入
此外Innodb还有另外两种锁 Insert Intention Locks、 AUTO-INC Locks