21 | 为什么我只改一行的语句,锁这么多?

间隙锁和 next-key lock 的概念,配合上行锁,是否会出现锁等待的问题犯错

加锁规则(可重复读),两“原则、两优化”、“bug”

1.  原则 1:加锁基本单位next-key lock,前开后闭区间

2.  原则 2:查找中访问对象才加锁

3.  优化 1:索引上等值查询唯一索引加锁,next-key lock退化为行锁

4.  优化 2:索引上等值查询,向右遍历时且最后一个值不满足等值条件,next-key lock退化为间隙锁

5.  一个 bug:唯一索范围查询不满足条件为止

建表和初始化语句

案例一:等值查询间隙锁

图 1 等值查询的间隙锁

没有 id=7 记录

1.  原则 1,加锁单位next-key 范围 (5,10]

2.  优化 2,等值查询 (id=7), id=10 不满足查询条件,next-key lock退化成间隙锁,加锁范围 (5,10)

B 被锁住, C 可以。

案例二:非唯一索引等值锁

图 2 只加在非唯一索引上的锁  

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;  加锁范围相同吗?

图 3 主键索引上范围查询的锁  

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。

图 4 非唯一索引范围锁

跟三不同:第一次c=10 定位,索引 c 加 (5,10] next-key

lock 后,非唯一索引, c 上 (5,10] 和 (10,15]  next-key lock。

B 堵住。扫到 c=15,才知道不需往后

案例五:唯一索引范围锁bug

加锁规则 bug 案例。

图 5 唯一索引范围锁的 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 上间隙状态,非唯一索引包含主键值,不可能存在“相同”两行。

图 6 非唯一索引等值的例子

两个 c=10 有间隙。跟间隙锁的开区间形式进行区别,我用 (c=10,id=30)表示索引上的一行。

delete加锁逻辑跟 select ...for update 类似,两个“原则”、两个“优化”和一个“bug”。

图 7 delete 示例

A 遍历先访问第一个 c=10。原则 1, (5,5)  (10,10) next-key lock

向右查找, (c=15,id=15)结束。优化 2,退化间隙锁。蓝色部分。

图 8 delete 加锁效果示例

案例七:limit 语句加锁

图 9 limit 语句加锁

A 加不加 limit 2,删除效果一样,加锁效果不同。遍历两条结束。

insert成功。删除数据时尽量limit。更安全,减小加锁范围

图 10 带 limit 2 的加锁效果  

案例八:一个死锁的例子

前面按照 next-key lock 分析,实际是间隙锁和行锁加起结果。

图 11 案例八的操作序列  

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被锁住?

图 12 锁分析思考题

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

你可能感兴趣的:(21 | 为什么我只改一行的语句,锁这么多?)