间隙锁和 next-key lock 的概念,配合上行锁,是否会出现锁等待的问题犯错。
加锁规则(可重复读),两“原则”、两“优化”、一“bug”。
1. 原则 1:加锁基本单位next-key lock,前开后闭区间
2. 原则 2:查找中访问对象才加锁
3. 优化 1:索引上等值查询,唯一索引加锁,next-key lock退化为行锁。
4. 优化 2:索引上等值查询,向右遍历时且最后一个值不满足等值条件,next-key lock退化为间隙锁。
5. 一个 bug:唯一索范围查询到不满足条件为止。
案例一:等值查询间隙锁
没有 id=7 记录
1. 原则 1,加锁单位next-key 范围 (5,10];
2. 优化 2,等值查询 (id=7), id=10 不满足查询条件,next-key lock退化成间隙锁,加锁范围 (5,10)。
B 被锁住, C 可以。
案例二:非唯一索引等值锁
1. 原则 1,(0,5] next-key lock。
2. c 普通索引,向右遍历, c=10 放弃。原则 2, (5,10] 加 next-key lock。
3. 优化 2:等值判断,向右遍历,不满足 c=5 ,退化成间隙锁 (5,10)。
4. 原则 2 ,只有访问到的对象才会加锁,查询用覆盖索引,主键索引没加锁,B 完成。
C 插入被 A 间隙锁 (5,10) 锁住。
lock in share mode: 只锁覆盖索引, 行加读锁避免数据被更新,要绕过覆盖索引优化,查询字段中加入索引中不存在字段。将 A 改select d from t where c=5 lock in share mode。
for update:系统认为要更新数据,主键索引加行锁(满足条件行);锁是加在索引上
案例三:主键索引范围锁
mysql> select * from t where id=10 for update;
mysql> select * from t where id>=10 and id<11 for update; 加锁范围相同吗?
1. 找id=10 行,本该next-key lock(5,10]。 优化 1, 主键 id 上等值条件,退化成行锁,只加id=10 行锁。
2. 继续找,id=15 停,加 next-key lock(10,15]。
A 查找 id=10 时,等值查询,右扫描到 id=15,范围查询。
案例四:非唯一索引范围锁
两个范围查询加锁 where 用 c。
跟三不同:第一次c=10 定位,索引 c 加 (5,10] next-key
lock 后,非唯一索引, c 上 (5,10] 和 (10,15] next-key lock。
B 堵住。扫到 c=15,才知道不需往后
案例五:唯一索引范围锁bug
加锁规则 bug 案例。
原则 1 ,索引 id 上只加 (10,15] 这个 next-key lock,id 唯一键, id=15 停止了。
但实现上,扫描到不满足为止, id=20。 (15,20] 也被锁。B 、C被锁。
我认为这是个 bug
案例六:非唯一索引上存在"等值"的例子
mysql> insert into t values(30,10,30);
表里两个 c=10 行。 c 上间隙状态,非唯一索引上包含主键值,不可能存在“相同”两行。
两个 c=10 有间隙。跟间隙锁的开区间形式进行区别,我用 (c=10,id=30)表示索引上的一行。
delete加锁逻辑跟 select ...for update 类似,两个“原则”、两个“优化”和一个“bug”。
A 遍历先访问第一个 c=10。原则 1, (5,5) (10,10) next-key lock。
向右查找, (c=15,id=15)结束。优化 2,退化间隙锁。蓝色部分。
案例七:limit 语句加锁
A 加不加 limit 2,删除效果一样,加锁效果不同。遍历两条结束。
insert成功。删除数据时尽量limit。更安全,减小加锁范围
案例八:一个死锁的例子
前面按照 next-key lock 分析,实际是间隙锁和行锁加起结果。
c 加next-key lock(5,10] 和间隙锁 (10,15); B 等待;
插入 (8,8,8) 被B 间隙锁锁住。出现死锁,让B 回滚。
B 的 next-key lock 不是没申请成功?分成两步,加 (5,10) 间隙锁成功;c=10 行锁被锁
间隙锁和行锁两段执行
c是非唯一键索引,为什么不锁(10,15} 呢?(5,10]就被锁住,后面锁加不上去了
小结
next-key lock 间隙锁加行锁实现。
如读提交,去掉间隙锁,只剩行锁,外键还有间隙锁。
优点:执行完,释放不满足行上的锁,不需等事务提交。范围小,时间短
解决幻读同时,提升事务能力。
思考题:
6 条记录,为什么insert被锁住?
1. order by c desc,定位c=20 行,加上间隙锁 (20,25) 和 next-key lock (15,20]。
2. 索引 c 向左遍历(加了order by c desc,优化使用c<=20), c=10停 next-key lock (5,10],阻塞 B 。
3. c=20、c=15、c=10 都存在值, select *主键 id 加三个行锁。
锁范围就是:
1. 索引 c (5, 25);
2. 主键索引id=15、20 行锁。
评论1
CREATE TABLE z (
id INT PRIMARY KEY AUTO_INCREMENT,
b INT,
KEY b(b)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8;
INSERT INTO z (id, b) VALUES (1, 2), (3, 4), (5, 6), (7, 8), (9, 10);
session A
BEGIN; SELECT * FROM z WHERE b = 6 FOR UPDATE;
session B
INSERT INTO z VALUES (0, 4); //这里为什么会被锁住
b next-key lock (4, 6];索引向右遍历,再加间隙锁 (6,8); (b=6,id=5) 有行锁
https://helloworlde.github.io/blog/blog/MySQL/MySQL-%E4%B8%AD%E5%85%B3%E4%BA%8Egap-lock-next-key-lock-%E7%9A%84%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98.html
评论2
<= 到底是间隙锁还是行锁?
跟“执行过程”配合起来分析。找“第一个”等值去找,“下一个值”,范围查找
评论3
CREATE TABLE `t` (
`a` int(11) NOT NULL,
`b` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into t values(1,1),(2,2),(3,3),(4,4),(5,5); READ-COMMITTED
session A:
begin; update t set a=6 where b=1;
session B:
begin; update t set a=7 where b=2;
均成功,RC全表扫描update,逐行加锁,释放不符条件, A成功(1,1)加锁,B扫描(1,1)并尝试加锁时会被阻塞,为何还能执行成功?
read-commited下,update有“semi-consistent” read优化,碰被锁了行,读入最新版本,不满足跳过;满足锁等待
案例2:
session A:begin; update t set a=6 where b=1;
session B:begin; delete from t where b=2; -- 被阻塞
问题2:为何案例1 中的session B不会被阻塞,而案例2的却被session A的行数阻塞,update和delete都是全部扫描,难道加锁机制不一样?
这个策略,只对update有效,delete无效
评论4
案例五:唯一索引范围锁 bug
begin; select * from t where id>10 and id<=15 for update;
1、加锁范围(10,15]和(15,20];
2、10未加锁,delete from t where id=10;正常删除;
3、insert into t values(10,10,10); 等待
4、锁蔓延
ps:
select * from … for update 语句,优化器全表扫描,主键索引 next-key lock 全加上。
“有行”才会加行锁。没有命中行,就加 next-key lock。等值时,加上优化 2