MySQL 21 为什么我只改一行的语句,锁这么多?
上篇文章中,介绍了间隙锁和临键锁,但并未说明加锁规则。本文首先介绍加锁规则,由于间隙锁在可重复读隔离级别下才有效,因此接下来的内容默认在可重复读隔离级别下。
加锁规则(限5.x系列<=5.7.24, 8.0系列<=8.0.13):
原则1:加锁的基本单位是临键锁,是一个前开后闭区间;
原则2:查找过程中访问到的对象才会加锁;
优化1:索引上的等值查询,给唯一索引加锁的时候,临键锁退化为行锁;
优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,临键锁退化为间隙锁;
一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
后续例子用到的表:
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);
案例一:等值查询间隙锁

由于表中没有id=7的记录,用加锁规则判断:
根据原则1,加锁单位是临键锁,session A加锁范围是(5,10];
根据优化2,这是一个等值查询
id=7,而id=10不满足查询条件,临键锁退化成间隙锁,因此最终加锁范围是(5,10)。
所以session B要插入id=8的记录会被锁住,但是session C修改id=10这行是可以的。
案例二:非唯一索引等值锁

这里session A要给索引c上c=5这一行加读锁:
根据原则1,加锁单位是临键锁,因此会给(0,5]加临键锁;
由于c是普通索引,因此仅访问
c=5这一条记录不能马上停下来,需要向右遍历查到c=10才放弃。根据原则2,访问到的都要加锁,因此给(5,10]加临键锁;这个遍历符合优化2,由于最后一个值不满足
c=5这个等值条件,临键锁退化成间隙锁;根据原则2,只有访问到的对象才会加锁,这个查询使用覆盖索引,不需要访问主键索引,所以主键索引上不加任何锁,因此session B的update语句可以成功。
而session C的插入操作,会被session A的间隙锁(5,10)锁住。
在该案例中,lock in share mode只锁覆盖索引,但如果是for update就不同了,因为系统会认为接下来更新数据,会顺便给主键索引上满足条件的行加上行锁。
该案例说明,锁是加在索引上的,同时如果要用lock in share mode来给行加读锁避免数据被更新,就必须绕过覆盖索引的优化,在查询字段中加入索引中不存在的字段,比如将session A的查询语句改成select d from t where c=5 lock in share mode。
案例三:主键索引范围锁
考虑下面这两条查询语句,加锁范围是否相同:
select * from t where id=10 for update;
select * from t where id>=10 and id<11 for update;
在逻辑上,这两条查询语句等价,但加锁规则不太一样。看看第二个语句的加锁效果:

分析session A的加锁情况:
先找到第一个
id=10的行,本该加临键锁(5,10],根据优化1,主键是唯一索引,因此该临键锁退化成行锁,只加了id=10这一行的行锁;范围查找会继续往后找,找到
id=15这一行停下来,因此会加临键锁(10,15]。
这里需要注意的是,session A定位查找id=10的行的时候,是当做等值查询来判断的,而向右扫描到id=15的时候,用的是范围查询判断。
案例四:非唯一索引范围锁

由于索引c是非唯一索引,与案例三相比,没有优化规则,因此最终session A加的锁是:索引c上的(5,10]和(10,15]这两个临键锁。
案例五:唯一索引范围锁bug

session A是一个范围查询,按照原则1的话,应该是索引id上只加(10,15]这个临键锁,且由于id唯一,所以循环判断到id=15这一行就应该停止。
但是实现上,InnoDB会往前扫描到第一个不满足条件的行为止,即id=20,由于这是范围扫描,因此索引id上的(15,20]这个临键锁也会被锁上。
所以session B和session C的操作都会被锁住。
案例六:非唯一索引上存在等值的例子
接下来的例子,是为了更好说明间隙的概念。这里插入一条新纪录:
insert into t values(30,10,30);
新插入一行后,表里有两个c=10的行。由于非唯一索引上包含主键的值,所以不存在完全相同的两行,此时索引c:

索引c中两个c=10的记录之间,也是有间隙的。
接下来看例子:

session A在遍历时,先访问第一个c=10的记录,根据原则1,会加(c=5,id=5)到(c=10,id=10)的临键锁。之后继续向右查找,直到碰到(c=15,id=15)这一行,根据优化2,这是一个等值查询,向右查找到了不满足条件的行,会退化成(c=10,id=10)到(c=15,id=15)的间隙锁。
因此delete语句的加锁范围实际上如下:

虚线表示这是个开区间。
案例七:limit语句加锁
案例六的对照案例:

表t里c=10的记录只有两条,因此limit 2不影响删除效果,但会影响加锁效果。可以看到session B的插入语句通过,跟案例六结果不同。
这是因为加了limit 2后,遍历到(c=10,id=30)这一行后,满足条件的语句已经有两条,循环结束。
因此在该案例中,加锁范围如下:

该案例的指导意义就是,在删除数据的时候尽量加上limit。
案例八:一个死锁的例子
该案例目的是说明:临键锁实际上是间隙锁和行锁加起来的结果。

按顺序分析:
session A启动事务后,在索引c上加了(5,10]和(10,15)的锁;
session B的update语句要在索引c上加(5,10],进入锁等待;
session A要插入时被session B的间隙锁锁住。由于出现死锁,InnoDB会让session B回滚。
可能会有疑惑,session B的临键锁还没申请成功,为什么也会死锁?
因为session B的临键锁实际分为两步,先加(5,10)的间隙锁,加锁成功,然后加c=10的行锁才进入等待。
MySQL 21 为什么我只改一行的语句,锁这么多?的更多相关文章
- MySQL 笔记整理(19) --为什么我只查一行的语句,也执行这么慢?
笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> (本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除) 19) --为什么我只查一行的语句,也执行这么慢? 需要说明一下,如果M ...
- 谁说.NET没有GC调优?只改一行代码就让程序不再占用内存
经常看到有群友调侃"为什么搞Java的总在学习JVM调优?那是因为Java烂!我们.NET就不需要搞这些!"真的是这样吗?今天我就用一个案例来分析一下. 昨天,一位学生问了我一个问 ...
- mysql之workbench如何只导出(insert语句)数据
https://www.jianshu.com/p/a5cd14bc5499 1. 说明: 出发点: 由于特殊原因,我们只想导出数据库中的数据(insert into语句格式的),但是在网上找到的资源 ...
- linux上怎么切换不同版本的arm-linux-gcc?只需改一行函数
linux上怎么切换不同版本的arm-linux-gcc?只需改一行函数 ln -s /usr/local/arm/3.4.1/bin/arm-linux-gcc /usr/bin/arm-linux ...
- Mysql,重复字段只取其中一行
Mysql,重复字段只取其中一行 格式 : select 字段 from [表] where 其他字段 in (select 函数(其他字段) from [表] group by 相同字段) 示例如下 ...
- js封装的三级联动菜单(使用时只需要一行js代码)
前言 在实际的项目开发中,我们经常需要三级联动,比如省市区的选择,商品的三级分类的选择等等. 而网上却找不到一个代码完整.功能强大.使用简单的三级联动菜单,大都只是简单的讲了一下实现思路. 下面就给大 ...
- RecyclerView, ListView 只显示一行内容 问题解决
Adapter 中的data有多行,但是RecyclerView只显示一行. 原因出在item的layout xml, 用了自动生成的RelativeLayout, 她的默认高度height属性是ma ...
- 性能测试记录: ZZ 只改5行代码获得10倍吞吐量提升
首先得找台足够性能的机器来测试,性能不足时代码运行会出现各种奇怪的现象,导致浪费时间 文章: https://www.jianshu.com/p/4cd8596352ad 只改了5行代码吞吐量提升 ...
- ScrollView中嵌套GridView,ListView只显示一行的解决办法
转载:http://blog.csdn.net/luohai859/article/details/39347583 关于为什么只显示一行,个人理解是:如果单独使用GridView和ListView, ...
- 解决lScrollView嵌套ListView只显示一行的问题,listvie显示全部的item
ScrollView嵌套ListView只显示一行的问题 1.思路:给listview重新添加一个高度. listview的高度==listview.item的高度之和. 2.注意:关键是添加list ...
随机推荐
- 【.NET必读】RabbitMQ 4.0+重大变更!C#开发者必须掌握的6大升级要点
RabbitMQ 作为一款广受欢迎的消息队列中间件,近年来从 3.x 版本升级到 4.0+,带来了显著的功能增强和架构调整.与此同时,其官方 C# 客户端也从 6.x 版本跃升至 7.0,引入了全新的 ...
- 洛谷 P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III
洛谷 P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III 题目描述 给你一个长为 n 的序列 a,m 次询问,每次查询一个区间的众数的出现次数,强制 ...
- 洛谷P1333 瑞瑞的木棍 字符串 最短路
说在前面 用M↓写的第一篇题解,欢迎提出意见. 题目描述 瑞瑞有一堆的玩具木棍,每根木棍的两端分别被染上了某种颜色,现在他突然有了一个想法,想要把这些木棍连在一起拼成一条线,并且使得木棍与木棍相接触的 ...
- Linux | base64编码与解码命令
1.base64编码 (1)base64 file 功能:从指定的文件file中读取数据,编码为base64的字符串然后输出: (2)echo "string" | base64 ...
- Redis集群的三种姿势
一.Redis主从复制 主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主. 1.如何实现 新建三个配置文件,分别命名为redis_ ...
- Springboot笔记<9>使用Javaweb原生组件
使用Javaweb原生组件 Javaweb的三大组件:servlet,Filter,Listener,Servlet 3.0 提供了以下 3 个注解: @WebServlet:用于声明一个 Servl ...
- Grafana监控指标、日志与链路追踪数据采集到GreptimeDB的完整实践指南
以下是将Grafana监控指标.日志与链路追踪数据采集到GreptimeDB的完整实践指南,涵盖部署.运维.安全及扩展的全流程: 一.整体架构 图表 二.数据采集配置 1. 指标采集(Promethe ...
- MySQL查询优化的步骤
本文由 ChatMoney团队出品 MySQL查询优化是提高数据库性能的关键步骤之一.通过优化查询,可以减少数据库的负载,提高查询速度,从而提高整个应用程序的性能. 选择合适的索引 索引是数据库中用于 ...
- DRF之登录认证源码分析
DRF之登录认证源码分析 [一]引入 [1]表模型 from django.db import models # Create your models here. class UserInfo(mod ...
- hot100之哈希
两数之和(001) 先看代码 class Solution { HashMap<Integer, Integer> map = new HashMap<>(); public ...