Gap Lock以及next-key lock是为了解决幻读的。产生幻读的缘故原由是,行锁只能锁住行,但是新插入记载这个动作,要更新的是记载之间的“间隙”。因此,为了解决幻读问题,InnoDB只好引入新的锁,也就是间隙锁(Gap Lock)。间隙锁,锁的就是两个值之间的空隙。如下,表t,初始化插入了6个记载,这就产生了7个间隙。
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
复制代码
当你执行 select * from t where d=5 for update的时候,就不止是给数据库中已有的6个记载加上了行锁,还同时加了7个间隙锁。如许就确保了无法再插入新的记载。间隙锁之间是不存在冲突的,比方:
SessionASessionBbegin;select * from t where c=7 lock in share mode;begin;select * from t where c=7 for update; 这里 session B并不会被堵住。由于表t 里并没有 c=7这个记载,因此 session A加的是间隙锁(5,10)。而 sessionB 也是在这个间隙加的间隙锁。它们有共同的目标,即:保护这个间隙,不答应插入值。但它们之间是不冲突的。间隙锁和行锁合称 next-key lock, 每个 next-key lock是前开后闭区间。如果用 select * from t for update要把整个表所有记载锁起来,就形成了7个 next-key lock,分别是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。 间隙锁造成的死锁: 我用两个 session来模拟并发,并假设往表里插入一条 id=9的数据。
【1】session A 执行select … for update语句,由于id=9这一行并不存在,因此会加上间隙锁(5,10);
【2】session B 执行select … for update语句,同样会加上间隙锁(5,10),间隙锁之间不会冲突,因此这个语句可以执行成功;
【3】session B 试图插入一行(9,9,9),被session A的间隙锁挡住了,只好进入等待;
【4】session A试图插入一行(9,9,9),被session B的间隙锁挡住了;
间隙锁的引入,大概会导致同样的语句锁住更大的范围,这实在是影响了并发度的;
四、全表扫描