加锁规则
加锁规则里面,包含了两个”原则‘、两个“优化”和一个“bug”。
1.原则1:加锁基本单位是next-key lock。前开后闭区间。
2.原则2:查找过程中访问到的对象才会加锁。
3.优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为 行锁。
4.优化2:索引上的等值查询,向右遍历且最后一个不满足等值条件的时候,next-key lock退化为间隙锁。
5.一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
案例一:等值查询间隙锁
案例二:非唯一索引等值锁
根据原则 1,加锁单位是 next-key lock,因此会给 (0,5]加上 next-key lock。
要注意 c 是普通索引,因此仅访问 c=5 这一条记录是不能马上停下来的,需要向右遍历,查到 c=10 才放弃。
根据原则 2,访问到的都要加锁,因此要给 (5,10]加 next-key lock。但是同时这个符合优化 2:等值判断,向右遍历,最后一个值不满足 c=5 这个等值条件,因此退化成间隙锁 (5,10)。
根据原则 2 ,只有访问到的对象才会加锁,这个查询使用覆盖索引,并不需要访问主键索引,所以主键索引上没有加任何锁,这就是为什么 session B 的 update 语句可以执行完成。
lock in share mode只锁覆盖索引,但是如果是for update 就不一样。执行for update,会顺便给主键索引上满足条件的行加上锁。
案例三:主键索引范围锁
mysql> select * from t where id=10 for update;
mysql> select * from t where id>=10 and id<11 for update;
语句1,只会对语句10进行加锁
而语句2则是行锁id =10 和 next-key(10,15】
案例四:非唯一索引范围锁
加锁范围为(5,10] 和 (10,15] 这两个 next-key lock。
案例五:唯一索引范围锁bug
yi因为id是唯一键,所以循环判断id = 15这一行就停止了 。但是实现上,InnoDB会往前扫面第一个不满足条件的行为止,因此(16,20】这个next - key -lock也会被锁上。
案例六:非唯一索引上存在“等值”的例子
这时,session A 在遍历的时候,先访问第一个 c=10 的记录。
同样地,根据原则 1,这里加的是 (c=5,id=5) 到 (c=10,id=10) 这个 next-key lock。
然后,session A 向右查找,直到碰到 (c=15,id=15) 这一行,循环才结束。
根据优化 2,这是一个等值查询,向右查找到了不满足条件的行,所以会退化成 (c=10,id=10) 到 (c=15,id=15) 的间隙锁。
也就是说,这个 delete 语句在索引 c 上的加锁范围,就是下图中蓝色区域覆盖的部分。
案例七:limit语句加锁
这是因为,案例七里的 delete 语句明确加了 limit 2 的限制,因此在遍历到 (c=10, id=30) 这一行之后,满足条件的语句已经有两条,循环就结束了。因此,索引 c 上的加锁范围就变成了从(c=5,id=5) 到(c=10,id=30) 这个前开后闭区间,如下图所示:
在删除数据的时候尽量加limit。这样不仅可以控制删除数据的条数,让操作更安全,还可以减小加锁的范围。
案例八:一个死锁的例子
session A 启动事务后执行查询语句加 lock in share mode,在索引 c 上加了 next-key lock(5,10] 和间隙锁 (10,15);
session B 的 update 语句也要在索引 c 上加 next-key lock(5,10] ,进入锁等待;
然后 session A 要再插入 (8,8,8) 这一行,被 session B 的间隙锁锁住。由于出现了死锁,InnoDB 让 session B 回滚。
其实是这样的,session B 的“加 next-key lock(5,10] ”操作,实际上分成了两步,先是加 (5,10) 的间隙锁,加锁成功;然后加 c=10 的行锁,这时候才被锁住的。
实际上next-key lock 来分析。具体执行的时候,要分成间隙锁和行锁两端来执行的。
读已提交除在外键条件下不存在间隙锁,还有个优化,即:语句执行过程中加上的行锁,在语句执行完成后,就把“不满足条件的行”上的行锁直接释放了,不需要等到事务提交。