MySQL中的锁机制和MVCC

2023-11-18

在学习MySQL中的锁机制相关时搜集了几篇写得非常不错的博客,这里就不再花时间详细介绍,本篇仅做总结和重点部分摘录(摘自:传送门)。推荐先理解下面篇博客(写得很赞): https://www.cnblogs.com/crazylqy/p/7611069.html

概述

InnoDB的MVCC

  • 概述: MVCC(Multi-Version Concurrency Control)多版本并发控制,MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,它在不同的数据库引擎中有不同的实现。MySQL中MVCC只能在Repeatable Read(读可重复读)、Read Committed(读可提交)这两个隔离级别下工作。
  • 用途: MVCC实现的是普通读取不加锁,并且读写不冲突,根据28定律,通常大部分为读操作,避免了读操作的加锁可以大大提高性能
  • 原理:
  1. MVCC是通过保存了数据库某个时间的快照来实现的。也就是说当几个事务开启的时间不同,可能会出现同一时刻不同事务读取同一张表同一行记录是不一样的。这个机制也是可重复读的实现。

先看一个例子:

在一个与MySQL的连接中启动事务,读取tno为1的教师姓名,结果为tom(还未commit)
在这里插入图片描述
再启动第二个连接,将tno为1的教师名改成了jery

begin;
update teacher set tname="jery" where tno=1;
commit;

此时,事务已经提交,我们再次从第一个连接的事务中查询tno为1的教师姓名
在这里插入图片描述
结果依然为tom,并没有读取到最新修改的数据jery,原因就在于每个事务读取的都是专有的快照

  1. 在InnoDB引擎的数据库中,每一行记录后都有几个隐藏列来记录信息:

    先了解一下两个概念:

系统版本号: 每当启动一个事务时,系统版本号会递增。
事务版本号 事务开始时的系统版本号作为该事务的版本号,事务的版本号用于在select操作中与记录的DATA_TRX_ID字段做对比。

隐藏列:

DATA_TRX_ID: 记录了某行记录的系统版本号,每当事务commit对该行的修改操作时就会将。
DATA_ROLL_PTR: 记录了此行记录的回滚记录指针,找之前的历史版本就是通过它。
DELETE BIT: 标记此记录是否正在有事务删除它,最后真正的删除操作是在事务commit后。

  1. 增删改查中的MVCC操作:

select:①执行select操作时,InnoDB会查找到对应的数据行,并对比DATA_TRX_ID(版本号),要求数据行的版本必须小于等于事务的版本,如果当前数据行版本大于此事务版本,那么InnoDB会进入undo log中查找。确保当前事务读取的是事务之前存在的,或者是由当前事务创建或修改的行。 ② InnoDB会查找到对应的数据行后,查看DELETE BIT是否被定义,只允许未定义,或者删除的版本要大于此事务版本号。保证在执行此事务之前还未被删除当且仅当这两个条件都成立才允许返回select结果!

insert: InnoDB创建新记录,并以当前系统的版本号为新增记录的DATA_TRX_ID,如果需要回滚则丢弃undo log。

delete: InnoDB寻找到需要删除的记录,将此记录的DELETE BIT设置为系统当前版本号,若事务回滚则去除DELETE BIT定义的版本号,若事务提交则删除行。

update: InnoDB寻找到需要更新的行记录,复制了一条新的记录,新记录的版本ID为当前系统版本号,新记录的回滚指针指向原记录,将原记录的删除ID也设置为当前系统版本号。提交后则删除原记录,若回滚则删除复制的记录,并清除原记录的删除ID。

现在分析一下上一个例子:
假设当前tno=1的教师记录的DATA_TRX_ID = 2,那么第一个事务开启时系统版本号假设为3,在第一个事务中执行的查询操作只会读取DATA_TRX_ID <= 3的记录。此时第二个事务开启了,假设事务版本号为4,它执行了对该行数据的更新操作并提交了,新的记录中DATA_TRX_ID >= 4(期间可能还有其他事务的发送,使系统版本号增加)。

MVCC锁相关

  • 在MVCC中,读操作可以分成:快照读 (snapshot read)与当前读 (current read)。

快照读: 读取的是记录的可见版本,不加锁。
当前读: 读取的是记录的最新版本,并且会对读取的记录加上锁(有共享和排他锁),确保其他事务不会并发地修改这条记录。

快照读:简单的select操作属于快照读,不会加锁。 select * from table where id=1;

当前读:添加了关键字的特殊查询操作,或者update、delete、insert都属于当前读,需要加锁。这里的锁分为共享锁和排他锁(忘记概念了?传送门)。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
以上语句中除了第一条是共享锁(S锁),其他都是排他锁(X锁

  • 为什么增删改也是当前读? 因为要进行增删改之前都得先找到符合条件的行,找的过程不就是读嘛~为了保证数据的线程安全性,需要对当前行进行加锁,有时也会出现锁表。

  • lock in share modefor update有何区别?

  1. 前者为记录添加的是S锁,后者添加的是X锁。共享锁和快照锁都不会影响快照读
  2. 根据S锁和X锁的规则,当记录中有S锁时,其他事务允许快照读,或再添加一个S锁,但是不允许添加X锁,必须等所有S锁都被释放以后才能上X锁。
  3. 当记录中有X锁时,只允许快照读,不允许再添加X锁和S锁,直到该X锁释放(事务commit)。

SQL语句的加锁分析

首先介绍InnoDB中的锁。

Record lock: 给单挑索引的记录上锁,它锁的是索引而不是记录本身。如果没有指定主键索引,那么InnoDB会创建一个隐藏的主键索引,它本身是一个索引组织表。

Gap lock: 间隙锁,它是存在于某一条记录和前一条或者后一条之间间隙的锁,它只要是用于解决RR隔离级别下的幻读问题。举个例子:在b和a,b和c之间加入了间隙锁,那么b的前后相邻的位置都不能插入记录。
在这里插入图片描述

delete from t1 where id = 10;
  1. 在id是主键+隔离级别RC。(RR相同)
    在这里插入图片描述
    主键是唯一的,只需要在id=10的这条记录的主键上加X锁即可

  2. id是唯一索引+隔离级别RC。(RR相同)
    在这里插入图片描述
    关于索引的总结可以看我的另一篇博客,有助于理解:传送门

  • 这里根据唯一索引找到索引表中的记录,再根据记录中的主键去寻找真正的数据行,加了两个锁分别在id=10的主键上和name=d的唯一索引上。
  • 为什么要两个列都加上锁? 如果只给唯一索引上了锁,那么并发事务来了个where条件为name=d的update操作,那么此update并不知道该记录已经被delete操作锁定,违背了同一记录上的更新和删除操作串行执行的约束。
  1. id是非唯一索引+隔离级别为RC
    在这里插入图片描述
    同理,非唯一索引可能搜索到的结果有好几个记录,那么对所有满足的记录都加上锁。主键和非唯一索引都会上锁。

  2. id不是索引+隔离级别RC

在这里插入图片描述
由于条件中的id不是索引,那么InnoDB将会根据主键进行全表的遍历扫描,所有的记录的主键都会被加上X锁,即便在MySQL中有相关的优化,它会判断每条记录是否满足条件,如果不满足则会释放锁,直到最后加锁的是符合条件的记录。但是仍然无法避免对不满足条件的主键的加锁、释放锁的步骤。

  1. id是非唯一索引+隔离级别为RR

    先回顾一下隔离级别,RC中允许存在幻读和不可重复读,RR中解决了幻读和不可重复读,其中可重复读的实现是通过快照,幻读的解决则是通过MVCC。这个情况就是对幻读预防的原理。

在这里插入图片描述
我们将例子中的SQL语句换为查询会更好理解:

begin;
select * from T1 where id=10 for update;
commit;

如图所示,在X锁的基础上加入了gap锁,它将非唯一索引之间、之前、之后的间隙都锁定上了,这意味着在这一次事务commit之前,其他事务不能再插入id=10的记录,更不可能去删除。那么在这一次的事务中重复执行该当前读语句,只能读取到快照的版本或者该事务自身修改的记录,也就杜绝了幻读!

  1. id不是索引+隔离级别RR

在这里插入图片描述
这个的情况和RC的类似,只是更可怕了,除了全表的X锁还有全表的gap锁,虽然也有类似的优化机制,会主动释放与条件不符合的索引的锁,但是性能依然不可观。这也是我们写SQL语句时需要避免的情况。

RR隔离级别是如何解决幻读的?

通过gap锁,将可能重复的记录之间的间隙锁上,其他事务无法并发的往间隙中进行插入。通过X锁锁定索引,其他事务无法并发进行删除。通过读取快照,每次只能读取到在此事务之前的历史版本或此事务修改的数据,实现可重复读。

死锁

简单的表结构。
在这里插入图片描述
情况一:

现有两个事务启动,T1和T2,对teacher表进行操作。顺序如图所示:
在这里插入图片描述
执行到③时T1等待T2,执行到④时T2又会等待T1,互相等待就造成了死锁。

情况二:

在这里插入图片描述
两个事务都只有一条SQL语句,但是仍然有可能造成死锁,原因在于事务对索引的加锁是逐个加锁。下面详细分析出现死锁时的情况:

  1. session1的加锁顺序为(1,hdc,100),(6,hdc,10)。session的加锁顺序以此类推。
  2. 那么在特定的情况下出现了如下的顺序:S1(1,hdc,100)—> S2(6,hdc,10)----> S1(6,hdc,10)—> S2(1,hdc,100)。也就出现了死锁。

简单总结: 从上面的两个例子中可以发现,死锁的发生关键在于并发下事务加锁的顺序。

模拟死锁

现在使用Springboot+Mybatis简单搭建环境操作数据库来模拟死锁。

teacher表结构:
在这里插入图片描述
TeacherMappper.xml

	<select id="getTeacherByLock" resultType="com.springboot.bean.Teacher">
        select * from teacher where tno=#{tno} for update;
    </select>

    <update id="updateTeacherByLock">
        update teacher set tname=#{tname} where tno=#{tno};
    </update>

TeacherMapper.java接口中的方法:

    Teacher getTeacherByLock(int tno);

    Teacher updateTeacherByLock(Teacher teacher);

TeacherService.java中模拟死锁的业务逻辑,这里为了方便调试起见写了两个方法:

@Service
public class TeacherService {
    @Autowired
    TeacherMapper mapper;

    @Transactional
    public void deadLock(){
        //锁tno=1的
        Teacher teacher = mapper.getTeacherByLock(1);
        //更新tno=2的
        Teacher teacher2 = new Teacher();
        teacher2.setTno(2);
        teacher2.setTname("aaa");
        mapper.updateTeacher(teacher2);
        System.out.println();
    }

    @Transactional
    public void deadLock2(){

        //锁tno=2的
        Teacher teacher = mapper.getTeacherByLock(2);

        //更新tno=1的
        Teacher teacher2 = new Teacher();
        teacher2.setTno(1);
        teacher2.setTname("bbb");
        mapper.updateTeacher(teacher2);
        System.out.println();
    }

}

测试类:

@Test
    public void contextLoads() {
        service.deadLock();
    }


    @Test
    public void contextLoads2() {
        service.deadLock2();
    }

以断点调试的方式启动,断点打在service层的方法中
在这里插入图片描述

分别debug启动两个test。调试顺序为:

  1. test1执行锁tno=1的索引
  2. test2执行锁tno=2的索引
  3. test1执行更新tno=2的tname,此时会进入等待test2释放锁,超时时间可以设置。
  4. test2执行更新tno=1的tname,此时会出现报错信息,同时test2事务回滚。
    在这里插入图片描述在test2尝试为tno=1的索引加锁时,InnoDB检测到了死锁并回滚了事务。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MySQL中的锁机制和MVCC 的相关文章

  • 国内首部

    当前 税务和发票等财税数据作为财务关联性强 欺诈难度大 覆盖率最高的优质数据 正成为数字普惠金融不可或缺的 硬核 力量 全面提升相关数据理论与实战能力正逢其时 8月8日 在金蝶2023年全球创见者大会 企业数字信用平行论坛 现场 金蝶征信
  • java使用MD5生成摘要

    对value进行hash处理 return hash处理结果 public static String digest String input int length 32 try MessageDigest md MessageDigest
  • openGL之API学习(六十八)core profile、compatibility profile、forward compatibility

    在OpenGL的发展历程中 总是兼顾向下兼容的特性 但是到了一定的程度之后 这些旧有的OpenGL API不再适应时代的需要 还有一些扩展并不是驱动一定要实现的扩展 这些被统一划入可选的Compatibility Profile 而由Ope
  • 信号的时域相位、频域相位

    文章目录 傅里叶变换的时移性质 matlab代码 单点频信号 线调信号 时域相位 频域相位 傅里叶变换的时移性质 信号增加线性相位时 是所有的频率分量对应的相位都有变化 matlab代码 清空一切 clc clear all close a
  • 翻译:《实用的Python编程》01_07_Functions

    目录 上一节 1 6 文件 下一节 2 0 处理数据 1 7 函数 随着程序开始变大 我们会想要有条理地组织这些程序 本节简要介绍函数 库模块以及带有异常的错误处理 自定义函数 对你要重用的代码使用函数 下面是函数的定义方式 def sum
  • 数据结构--图的学习(基础概念)

    目录 图的定义 图的逻辑结构应用 无向图 有向图 编辑 简单图 多重图 顶点的度 入读 出度 顶点 顶点的关系描述 连通图和强连通图 子图 1 无向图的子图 编辑 2 有向图的子图 连通分量 强连通分量 生成树 生成森林 边的权 带权图 网
  • 2014年3月7日星期五(DEMO8-4,实体三角形着色)

    这个例子比较大 任重而道远 理论草草看了下 光记住索引和RGB各项求最小距离了 为简单起见 拷贝7 6过来 并把8 3封装到的引擎代码拷贝替换 逐行来看 各个击破 先换成800 600视口 define SCREEN WIDTH 800 d
  • mysql之基础查询,条件查询测试题07

    1 基础查询 条件查询测试题 看图 结果 1 SELECT salary last name FROM employees WHERE commission pct IS NULL AND salary lt 18000 2 SELECT
  • STM32F407 CAN1 CAN2

    注意CAN1可以单独使用 CAN2要开启CAN1时钟才能使用 u8 CAN1 Mode Init u8 mode GPIO InitTypeDef GPIO InitStructure CAN InitTypeDef CAN InitStr
  • DBeaver 如何配置离线驱动

    1 打开已经下载过DBeaver驱动的软件 点击 数据库 驱动管理器 进入 2 选中 MySQL 点击右侧的 编辑 进入 3 点击页签 库 打开列表内容 选中 mysql connector java 8 0 17 jar 点击 类路径 4
  • Android用surface直接显示yuv数据(二)

    研究了一段时间Android的surface系统 一直执着地认为所有在surface或者屏幕上显示的画面 必须要转换成RGB才能显示 yuv数据也要通过颜色空间转换成RGB才能显示 可最近在研究stagefright视频显示时发现 根本找不
  • 泰克

    主要功能 硬件连接 参数配置 运行参数 数据查询 程控对象 示波器 程控接口 兼容市面上所有具有GPIB USB RS232 LAN RS485 TTL任意一种程控接口的示波器 仪器兼容性 泰克Tektronix 是德科技Keysight
  • CISC RISC ARM MIPS区别与联系

    转载 文章有点老 不过还是有所收获 补充一句 国内那么多牛人研究处理器架构 要钱有钱 要人有人 ARM都能获得如此成就 为啥国产那么多CPU团队都不成功 其它类型的芯片也一样 大多都是买国外IP 个人理解 相关专利太少 也就是积累不足 无法
  • 【MATLAB第31期】基于MATLAB的降维/全局敏感性分析/特征排序/数据处理回归问题MATLAB代码实现(持续更新)

    MATLAB第31期 基于MATLAB的降维 全局敏感性分析 特征排序 数据处理回归问题MATLAB代码实现 持续更新 本文敏感性分析主要分析回归问题 下期分析分类问题 fisher rf arf nca等 内容更新 增加视频解说 基于MA
  • npm创建openlayer(入门)

    1 搭建node js开发环境 2 创建目录 mkdir ol deme 3 安装ol包 npx create ol app 4 打开 npm start
  • flex布局和grid布局

    flex布局 父容器 parent 8个设置项 parent display flex flex direction row row reverse column column reverse 决定子元素的排列方式 flex wrap no
  • 服务器系统磁盘分区,服务器磁盘分区教程

    服务器磁盘分区教程 内容精选 换一换 对于Linux弹性云服务器 磁盘分区与磁盘设备名称完全对应 本节以Red Hat Enterprise Linux 7的图形界面为例 介绍查询Linux弹性云服务器磁盘分区与磁盘设备的方法 以root权
  • pandas导入和导出数据至MySQL数据库

    安装准备 可以不用看 同学们在进行数据分析 数据处理时经常会使用pandas来储存结构化数据 我们在数据处理中会出现各种的过程文件或最终文件 虽然pandas给我们提供了非常多的输出结口 比如csv文件 xlsx文件 但在软件开发过程中db
  • 关于httpurlconnection getcontentlength返回值为-1的问题

    在做AsyncTask异步任务读取网络数据的时候 发现了httpUrlConnection的获取数据内容长度 connection getContentLength 总是为 1 导致进度条一直为灰色状态 预期结果应如图所示 而现在的效果是这
  • 网络协议的三要素

    一个网络协议主要由语法 语义和同步三个要素组成 1 语法 语法指数据与控制信息的结构或格式 确定通信时采用的数据格式 编码及信号电平等 2 语义 语义由通信过程的说明构成 它规定了需要发出何种控制信息 完成何种控制动作以及做出何种应答 对发

随机推荐