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 ...
随机推荐
- N+1查询:数据库性能的隐形杀手与终极拯救指南
title: N+1查询:数据库性能的隐形杀手与终极拯救指南 date: 2025/05/06 00:16:30 updated: 2025/05/06 00:16:30 author: cmdrag ...
- 【语义分割专栏】先导篇:常用数据集(VOC、Camvid、Cityscape、ADE20k、COCO)
目录 前言 mask模式 PASCAL-VOC2012 下载 数据集简介 数据加载(dataloader) CamVid 下载 数据集简介 数据加载(dataloader) Cityscape 下载 ...
- FastAPI与Alembic:数据库迁移的隐秘艺术
title: FastAPI与Alembic:数据库迁移的隐秘艺术 date: 2025/05/13 02:02:31 updated: 2025/05/13 02:02:31 author: cmd ...
- FMEA方法,排除架构可用性隐患的利器
极客时间:<从 0 开始学架构>:FMEA方法,排除架构可用性隐患的利器 FMEA 方法,就是保证我们做到全面分析的一个非常简单但是非常有效的方法. 1.FMEA 介绍 FMEA(Fail ...
- Go语言flag包:命令行解析
转载:http://c.biancheng.net/view/5573.html 在编写命令行程序(工具.server)时,需要对命令行参数进行解析,各种编程语言一般都会提供解析命令行参数的方法或库, ...
- C#中的弱引用
弱引用保持的是一个GC"不可见"的引用,是指弱引用不会增加对象的引用计数,也不会阻止垃圾回收器对该对象进行回收.因此,弱引用的目标对象可以被垃圾回收器回收,而弱引用本身不会对垃圾回 ...
- RBMQ中python案例一:简单模式
一.生产者与消费者模式之 简单模式,原理图 二.生产者产生消息 import json import pika import datetime # 生产者 producer.py def get_me ...
- sublime text 3 c++配置(编译+运行)
之前在网上找了很多配置教程都没成功,要么只能编译,要么只能运行编译好后的exe,没办法一键运行. 方法: 操作方式: 点击**工具,再选编译系统,再选新建编译系统** 然后,把下面的代码,全部复制,并 ...
- Java中的静态块(static{})
静态块(static{}) (1) static关键字还有一个比较关键的作用,用来形成静态代码块(static{} 即static块 )以优化程序性能. (2) static块可以置于类中的任何地方, ...
- 用网页计数器来说明application和session
jsp的代码: 1 <body> 2 <h1>网页计数器</h1> 3 <% 4 //第一次访问数据为空 5 Object obj=application.g ...