文章插图
上图基本流程如下:
文章插图
从上面的实测也可以看出 , 普通索引下添加x锁 , 居然会加一个gap锁 , 而且这个gap区间是前一个记录(并包含它) , 到下一个记录
如 uid = 20 , 前后两个记录为(1, 10), (10, 30)
- gap lock: 范围为 [10, 30)
- 因此无法插入uid=[10,30)
- 注意 , uid=10上有gap锁只是不能插入记录 , 但是加X锁是没有问题的(有兴趣的可以测试一下)
当我们锁的记录不存在时 , 锁情况如下:
文章插图
【一次并发插入死锁带来的“教训”,我才清楚这些MySQL锁知识】实测case如下(TN省略 , 锁全表的没啥测试必要性)
基本流程就不画图了 , 上面图中已经有文字描述了
文章插图
从上面的测试也可以看出 , uid=30没有被锁住 , 这里只在uid=(20, 30)这一区间添加了gap锁
唯一索引与普通索引表现一致 , 会阻塞insert的插入意向锁(后面说这个东西)
3.4 范围查询
当我们锁一段区间时 , 锁的情况如下:
文章插图
文章插图
简单来说 , 范围查询时 , 添加next key lock , 根据我们的查询条件 , 找到最左边和最右边的记录区间
如 uid > 15 and uid < 25 , 找到的记录是(1, 10), (10, 30)
- gap锁为(10, 30)
- next key lock会为右边添加行锁 , 即uid=30加X锁
- 因此针对uid=30记录加锁会被阻塞(但是针对uid=28,29加x锁则不会被阻塞 , 插入会 , 有兴趣的小伙伴可以实测一下)
3.5 小结
在RR隔离级别中 , 我们一般认为可以产生锁的语句为:
- SELECT ... FOR UPDATE: X锁
- SELECT ... LOCK IN SHARE MODE: S锁
- update/delete: X锁
文章插图
| 普通索引 | 精确匹配 , 且命中 | 行锁 + gap lock (上一个记录和下个记录区间 , 左闭右开 , 左边记录非行锁) | 普通索引 | 精确匹配 , 未命中 | gap lock | | 普通索引 | 范围查询 | next key lock |
4. 锁冲突
上面介绍了不同场景下会产生什么样的锁 , 但是看完之后会有一个疑问 , 针对行锁其他会话竞争的时候 , 可以按照X/S锁的规则来 , 但是这个GAP LOCK貌似只针对insert有效 , insert除了加X锁之外是不是还有其他的特殊逻辑?
4.1 插入意向锁
插入意向锁其实是一种特殊的 gap lock , 但是它不会阻塞其他锁 。假设存在值为 4 和 7 的索引记录 , 尝试插入值 5 和 6 的两个事务在获取插入行上的排它锁之前使用插入意向锁锁定间隙 , 即在(4 , 7)上加 gap lock , 但是这两个事务不会互相冲突等待;但是如果这个区间存在gap lock , 则会被阻塞;如果多个事务插入相同数据导致唯一冲突 , 则在重复的索引记录上加读锁
简单来说 , 它的属性为:
- 它不会阻塞其他任何锁;
- 它本身仅会被 gap lock 阻塞
- 通常insert语句 , 加的是行锁 , 排它锁
- 在insert之前 , 先通过插入意向锁 , 判断是否可以插入(仅会被gap lock阻塞)
- 当插入唯一冲突时 , 在重复索引上添加读锁
- 原因如下:
- 事务1 插入成功未提交 , 获取了排它锁 , 但是事务1最终可能会回滚 , 所以其他重复插入事务不应该直接失败 , 这个时候他们改为申请读锁(疑问点:为什么要改成读锁呢?)
推荐阅读
- JS排序算法:冒泡、选择、插入、归并、快速、希尔、堆、计数
- 清朝男人为什么梳辫子 清朝男人辫子多久洗一次
- |钓一次鱼要用十几斤玉米?野钓翘嘴鱼窝料不在多,打窝节奏很关键
- 汽车刹车片多久换一次才是最安全
- 一次不经意的相遇,让我们?今天是我们相识的第100天
- 2.春种一粒粟?种一粒粟春种一粒粟
- Mybatis 批量插入万条数据
- 高并发服务遇Redis瓶颈引发的事故
- 一次难忘的滑雪经历?滑雪场的奇妙邂逅
- 最早出使西域为开辟丝绸之路做出贡献的是谁 张第一次出使西域的主要目的是什么