Mysql:行锁,间隙锁,next-key锁?
注:以下讨论基于InnoDB引擎。
文章目录
- 问题引入
- 猜想1:只加了一行写锁,锁住要修改的这一行。
- 语义问题
- 数据一致性问题
- 猜想2:要修改的这一行加写锁,扫描过程中遇到其它行加读锁
- 猜想3:要修改的这一行加写锁,扫描过程中遇到其它行加读锁,行与行之间的间隙加上间隙锁。
- next-key锁
- next-key锁带来的问题
- 总结
问题引入
创建一张表t,并插入一些数据
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);
表中数据如下

执行以下sql:
select * from t where d = 5 for update;
问:这句sql是当前读,读的是d=5的这一行,我们都知道,当前读的时候会加锁。所以这句sql在读 d=5这一行时,会在该行上加写锁。现在问题来了,除了在d=5这一行上加上写锁之外,还会加其他锁吗?
猜想1:只加了一行写锁,锁住要修改的这一行。
| 时间 | 事务A | 事务B | 事务C |
|---|---|---|---|
| t0 | begin; | ||
| t1 | select * from t where d = 5 for update; (5, 5, 5) | ||
| t2 | update t set d = 5 where id = 0; | ||
| t3 | select * from t where d = 5 for update; (0, 0, 5) (5, 5, 5) | ||
| t4 | insert into t values(1,1,5); | ||
| t5 | select * from t where d = 5 for update; (0, 0, 5) (1, 1, 5) (5, 5, 5) | ||
| t6 | commit; |
如果猜想一正确,事务B和事务C不会被阻塞,事务A三次select结果显示在表中高亮部分。我们来分析以下这三条结果:
- t1时刻,表中只有一行d=5,所以返回一行
- t2时刻,事务B将id=0的一行的d值修改为5,所以查出来是两行。这里发生了不可重复读问题
- t3时刻,事务C插入了一行,所以查出来d=5有三行。这里发生了幻读问题。即前后两次查询中,后一次查询看到了前一次查询没有看到的行。
注意:t2时刻不是幻读,因为幻读只是针对插入新行。
你也许会说,for update是当前读,事务B和事务C修改了表,就应该看到最新修改的结果啊。
我们再来看看如果按猜想1这样设计,会导致什么问题果:
语义问题
| 时间 | 事务A | 事务B | 事务C |
|---|---|---|---|
| t0 | begin; | ||
| t1 | select * from t where d = 5 for update; | ||
| t2 | update t set d = 5 where id = 0; update t set c = 5 where id = 0; | ||
| t3 | select * from t where d = 5 for update; | ||
| t4 | insert into t values(1,1,5); update t set c = 5 where id = 1; | ||
| t5 | select * from t where d = 5 for update; | ||
| t6 | commit; |
t1时刻,事务A执行的语义是“我要将d=5的行加上行锁”
t2时刻,id=0这一行的d值等于5,按照猜想一,这一行是没有加锁的,所以事务B可以执行下面修改语句。
t4时刻,同理,事务C也可以修改新插入的行
事务B和事务C在执行各自第二个update语句时就破坏了事务A宣布的语义。
数据一致性问题
| 时间 | 事务A | 事务B | 事务C |
|---|---|---|---|
| t0 | begin; | ||
| t1 | select * from t where d = 5 for update; update t set d = 100 where d=5; | ||
| t2 | update t set d = 5 where id = 0; update t set c = 5 where id = 0; | ||
| t3 | select * from t where d = 5 for update; | ||
| t4 | insert into t values(1,1,5); update t set c = 5 where id = 1; | ||
| t5 | select * from t where d = 5 for update; | ||
| t6 | commit; |
此时数据库表中的d=5的行记录应该是:
| id | c | d |
|---|---|---|
| 0 | 5 | 5 |
| 1 | 5 | 5 |
底层binlog在记录时,会按照事务的提交顺序记录操作。事务的提交顺序是:B -> C -> A 所以,在binlog里面,日志是这样记录的:
// 事务B
update t set d = 5 where id = 0; // (0,0,5)
update t set c = 5 where id = 0; // (0,5,5)
// 事务C
insert into t values(1,1,5); // (1,1,5)
update t set c = 5 where id = 1; // (1,5,5)
// 事务A
select * from t where d = 5 for update; // ()
update t set d = 100 where d=5; // 所有d=5的行都把d值修改为100
select * from t where d = 5 for update;
select * from t where d = 5 for update;
如果拿这个binlog去备份备库,备库中就没有d=5的行了。而主库表中实际上是由两行d=5的记录的。这里发生了主库与备库数据不一致的问题。
综上所述吗,猜想1不正确。
猜想2:要修改的这一行加写锁,扫描过程中遇到其它行加读锁
| 时间 | 事务A | 事务B | 事务C |
|---|---|---|---|
| t0 | begin; | ||
| t1 | select * from t where d = 5 for update; update t set d = 100 where d = 5; | ||
| t2 | update t set d = 5 where id = 0; blocked update t set c = 5 where id = 0; | ||
| t3 | select * from t where d = 5 for update; | ||
| t4 | insert into t values(1,1,5); update t set c = 5 where id = 1; | ||
| t5 | select * from t where d = 5 for update; | ||
| t6 | commit; |
如果猜想2正确,t1时刻事务A执行后,会将扫描到的行都锁柱,所以t2时刻,事务B想要更新id=0的这一行记录,会被阻塞,只有A提交之后才会执行B。而t4时刻,事务C想表中插入行不会被阻塞,因为事务A在t1时刻只锁住了表中存在的行,而新插入的行在t1时刻还不存在,所以可以可以执行插入。同样地,在t5时刻,发生了幻读,即同一事务,前后两次查询,后一次查询看到了前一次查询没有看到的新行。
三个事务执行完,数据库表中的d=5的行应该是:
| id | c | d |
|---|---|---|
| 0 | 5 | 5 |
| 1 | 5 | 5 |
我们还是看看binlog日志里面是怎么记录的,由于B被阻塞,所以事务B只有等A提交之后才会执行,执行顺序应该是:C->A->B。
// 事务C
insert into t values(1,1,5); // (1,1,5)
update t set c = 5 where id = 1; // (1,5,5)
// 事务A
select * from t where d = 5 for update; // ()
update t set d = 100 where d=5; // 所有d=5的行都把d值修改为100
select * from t where d = 5 for update;
select * from t where d = 5 for update;
// 事务B
update t set d = 5 where id = 0; // (0,0,5)
update t set c = 5 where id = 0; // (0,5,5)
用binlog生成备库时,由于事务B是在事务A之后的,所以id=0这一行数据不一致问题解决了,但是id=1这一行数据还是不一致。
也就是说,即使把表中所有行都加上了锁,还是无法阻止并发事务插入新的记录。
猜想3:要修改的这一行加写锁,扫描过程中遇到其它行加读锁,行与行之间的间隙加上间隙锁。
我们再来看猜想3,猜想3是加锁最多的:
- d=5的行加了行锁
- 表中所有存在的行加了行锁
- 行与行的间隙加了间隙锁
我们来看看什么是间隙锁,表t初始化插入了6条记录:

6条记录就会产生7个间隙,如图所示

所以,当执行select * from t where d = 5 for update; 语句时,不仅加了6个行锁,还加了7个间隙锁。这样就确保了事务A执行过程中无法插入新的记录。
也就是说,在一行行扫描过程中,不仅给行加上了行锁,还给行两边的间隙加上了间隙锁。读锁和读锁之间不互斥,间隙锁也一样。
| 读锁 | 写锁 | |
|---|---|---|
| 读锁 | 不互斥 | 互斥 |
| 写锁 | 互斥 | 互斥 |
间隙锁与间隙锁之间是不互斥的,也就是说执行下面这个并发事务时:
| 时间 | 事务A | 事务B |
|---|---|---|
| t0 | begin; select * from t where id = 5 for update; | |
| t1 | begin; select * from t where id = 10 for update; |

事务B是不会被阻塞的,间隙锁只对insert语句生效。
next-key锁
还用t表说明,d上没有索引:

这里图中的数字是id:

where条件是d=25,由于d上没有索引,所以要扫描所有的行,扫描到的行都加上读锁。行与行之间的间隙加了间隙锁。要修改的d=25这一行(25,25,25)加上写锁。
next-key锁就是间隙锁(开区间)加 行锁(单值),变成一个左开右闭区间
比如 (-inf, 0)的间隙锁,加上id = 0这一行的行锁 就等于 (-inf, 0] 的next-key锁
next-key锁带来的问题
间隙锁和next-key锁虽然可以解决幻读问题,但是会带来其他困扰。
比如,对于下面的sql语句:
begin;
select * from t where id=N for update;
/*如果行不存在*/
insert into t values(N,N,N);
/*如果行存在*/
update t set d=N set id=N;
commit;
如果在两个事务并发的执行,可能会造成死锁,我们用表格来说明一下:
| 时间 | 事务A | 事务B |
|---|---|---|
| t0 | begin; select * from t where id=9 for update; | |
| t1 | begin; select * from t where id=9 for update; | |
| t2 | insert into t values(9,9,9); blocked | |
| t3 | insert into t values(9,9,9); blocked dead lock |
两个事务,A和B并发地执行上面的业务sql:
-
t0时刻,事务A开启事务,执行select * from t where id=9 for update; 由于id上有索引,索引只会定位到id=9这一行,不会扫描其他行,但是id=9这一行不存在,所以加不上写锁。同时,会给(5,10)这个间隙加上间隙锁。

-
t1时刻,事务B开启事务,执行select * from t where id=9 for update; 与A一样,只会给(5,10)加上间隙锁

-
t2时刻,当事务A执行inser操作,往(5,10)间隙插入行是,由于事务B也有间隙锁,所以会被阻塞,等事务B commit 释放锁之后才能执行插入操作
-
t3时刻,当事务B执行insert操作,往(5,10)间隙插入行是,由于事务A也有间隙锁,所以会被阻塞,等事务A commit 释放锁之后才能执行插入操作。事务A和事务B同时在等待对方的资源,才会释放自己占用的锁,造成死锁。
总结
在可重复读的隔离级别下,mysql的当前读加锁规则:
1、满足where条件的行加写锁
2、扫描过程中遇到的行加读锁
3、表中已存在的行会有间隙,扫描过程中遇到间隙会加间隙锁
相关文章:
Mysql:行锁,间隙锁,next-key锁?
注:以下讨论基于InnoDB引擎。 文章目录 问题引入猜想1:只加了一行写锁,锁住要修改的这一行。语义问题数据一致性问题 猜想2:要修改的这一行加写锁,扫描过程中遇到其它行加读锁猜想3:要修改的这一行加写锁&…...
Grass推出Layer 2 Data Rollup
Grass推出Layer 2 Data Rollup Grass邀请链接最新资讯 Grass邀请链接 欢迎使用我的邀请码进行注册: 邀请链接 如果你还不知道注册流程:详见Grass: 出售闲置带宽实现被动收入 最新资讯 简讯:2024年3月13日,Grass宣布正在建立基于Solana的La…...
[Java、Android面试]_04_进程、线程、协程
本人今年参加了很多面试,也有幸拿到了一些大厂的offer,整理了众多面试资料,后续还会分享众多面试资料。 整理成了面试系列,由于时间有限,每天整理一点,后续会陆续分享出来,感兴趣的朋友可收藏 文…...
MyLisp项目日志:解析用户输入与波兰表达式
文章目录 编程语言模拟自然语言定义名词和形容词定义短语定义句子 简化模拟过程正则表达式 波兰表达式及其解析波兰表达式语法描述波兰表达式语法解析解析用户输入 v0.0.2 编程语言 编程语言是类似于自然语言的,虽然我们是自然而然就学会了自己的母语,但…...
torch.backends.cudnn.benchmark 作用
相关参数 torch.backends.cudnn.enabled torch.backends.cudnn.benchmark torch.backends.cudnn.deterministictorch.backends.cudnn.benchmark True:将会让程序在开始时花费一点额外时间,为整个网络的每个卷积层搜索最适合它的卷积实现算法,…...
vue的$nextTick应用场景
文章目录 $nextTick有什么作用?一、NextTick是什么二、为什么要有nextTick? $nextTick有什么作用? 一、NextTick是什么 官方对其的定义 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的…...
springboot RestTemplate 发送xml、接收xml、pojo中的属性转为属性
背景 调用第三方接口时,它们的系统比较老,只支持接收xml而不支持json,默认的springboot RestTemplate不支持发送xml,添加依赖就可以解决这个问题。 添加jackson-dataformat-xml依赖 FasterXML/jackson-dataformat-xml是一个xml…...
Lua-Lua与C++的交互2
Lua与C的交互是指在C程序中使用Lua本语言,或者在Lua脚本中调用C代码的过程。这种交互可以实现C与Lua之间的数据传递和函数调用。 在C中与Lua交互的主要步骤如下: 引入Lua库:首先需要在C程序中引入Lua的头文件和库文件,以便能够使…...
学python新手如何安装pycharm;python小白如何安装pycharm
首先找到官网: Download PyCharm: The Python IDE for data science and web development by JetBrains 打开后选择下载,下图标红部分 点击exe程序,点击下一步! 选择安装路径,下一步 弹出界面全选 选择默认 然后直接…...
Oracle Primavera P6 数据库升级
前言 为了模拟各种P6测试,我常常会安装各种不同版本的p6系统,无论是P6服务,亦或是P6客户端工具Professional,在今天操作p6使用时,无意识到安装在本地的P6 数据库(21.12)出现了与Professional软…...
共享库的创建gcc选项“-shared -fPIC -WI”
共享库的创建非常简单,最关键的是gcc的几个参数: “-shared”: 表示输出结果是共享库类型。编译选项告诉编译器生成一个共享库(也称为动态链接库或 DLL)。共享库是一种包含可重用代码和数据的二进制文件,…...
微服务:Bot代码执行
每次要多传一个bot_id 判网关的时候判127.0.0.1所以最好改localhost 创建SpringCloud的子项目 BotRunningSystem 在BotRunningSystem项目中添加依赖: joor-java-8 可动态编译Java代码 2. 修改前端,传入对Bot的选择操作 package com.kob.botrunningsy…...
Python 导入Excel三维坐标数据 生成三维曲面地形图(面) 3、线条平滑曲面但有条纹
环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 pandas 2.1.4 openpyxl 3.1.2 scipy 1.12.0 代码: import pandas as pd import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.interpolate import griddata imp…...
Vue.js+SpringBoot开发数字化社区网格管理系统
目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、开发背景四、系统展示五、核心源码5.1 查询企事业单位5.2 查询流动人口5.3 查询精准扶贫5.4 查询案件5.5 查询人口 六、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的数字化社区网格管理系统…...
java SSM农产品订购网站系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计
一、源码特点 java SSM农产品订购网站系统是一套完善的web设计系统(系统采用SSM框架进行设计开发,springspringMVCmybatis),对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采…...
vsto快速在excel中查找某个字符串
是的,使用foreach循环遍历 Excel.Range 可能会较慢,特别是在大型数据集上。为了提高效率,你可以考虑使用 Value 属性一次性获取整个范围的值,然后在内存中搜索文本。这样可以减少与 Excel 之间的交互次数,提高性能。 …...
Unity类银河恶魔城学习记录10-1 10-2 P89,90 Character stats - Stat script源代码
Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Stat.cs using System.Collections; using System.Collections.Generic; us…...
西门子TIA中配置Anybus PROFINET IO Slave 模块
1、所需产品 Siemens S7 PLC CPU 315-2 PN/DP 6ES7 315-2EH-0AB0 Siemens PLC 编程电缆 n.a. n.a. PC ,并安装Siemens PLC编程软件 TIA Portal V11 X-gateway Slave 接口的GSDML文件 根据网关的软件版本而定 Anybus Communicator GSD文件 GSDML-V1.0-HMS-ABCPRT-20050317.xl…...
在 Rust 中使用 Serde 处理json
在 Rust 中使用 Serde 处理json 在本文中,我们将讨论 Serde、如何在 Rust 应用程序中使用它以及一些更高级的提示和技巧。 什么是serde? Rust中的serde crate用于高效地序列化和反序列化多种格式的数据。它通过提供两个可以使用的traits来实现这一点&a…...
【数据库】数据库介绍
文章目录 一、数据库介绍二、SQL分类 一、数据库介绍 什么是数据库 存储数据用文件就可以了,为什么还要弄个数据库? 文件保存数据有以下几个缺点: 文件的安全性问题 文件不利于数据查询和管理 文件不利于存储海量数据 文件在程序中控制不方便 数据库存…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...
聚六亚甲基单胍盐酸盐市场深度解析:现状、挑战与机遇
根据 QYResearch 发布的市场报告显示,全球市场规模预计在 2031 年达到 9848 万美元,2025 - 2031 年期间年复合增长率(CAGR)为 3.7%。在竞争格局上,市场集中度较高,2024 年全球前十强厂商占据约 74.0% 的市场…...
Mysql故障排插与环境优化
前置知识点 最上层是一些客户端和连接服务,包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念,为通过安全认证接入的客户端提供线程。同样在该层上可…...
内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献
Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译: ### 胃肠道癌症的发病率呈上升趋势,且有年轻化倾向(Bray等人,2018&#x…...
