MySQL 记录锁+间隙锁可以防止删除操作而导致的幻读吗?

news2024/9/22 13:41:36

文章目录

  • 什么是幻读?
  • 实验验证
  • 加锁分析
  • 总结

什么是幻读?

首先来看看 MySQL 文档是怎么定义幻读(Phantom Read)的:

The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times.
For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.

翻译:当同一个查询在不同的时间产生不同的结果集时,事务中就会出现所谓的幻象问题。例如,如果 SELECT 执行了两次,但第二次返回了第一次没有返回的行,则该行是“幻像”行。

举个例子,假设一个事务在 T1 时刻和 T2 时刻分别执行了下面查询语句,途中没有执行其他任何语句:

SELECT * FROM t_test WHERE id > 100;

只要 T1 和 T2 时刻执行产生的结果集是不相同的,那就发生了幻读的问题,比如:

T1 时间执行的结果是有 5 条行记录,而 T2 时间执行的结果是有 6 条行记录,那就发生了幻读的问题。
T1 时间执行的结果是有 5 条行记录,而 T2 时间执行的结果是有 4 条行记录,也是发生了幻读的问题。

MySQL 是怎么解决幻读的?

MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了,详见这篇文章 (opens new window)),解决的方案有两种:

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
  • 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

实验验证

接下来,来验证「 MySQL 记录锁+间隙锁可以防止删除操作而导致的幻读问题」的结论。

实验环境:MySQL 8.0 版本,可重复读隔离级。

现在有一张用户表(t_user),表里只有一个主键索引,表里有以下行数据:

在这里插入图片描述
现在有一个 A 事务执行了一条查询语句,查询到年龄大于 20 岁的用户共有 6 条行记录。

在这里插入图片描述

此时,B 事务的删除语句就陷入了等待状态,说明是无法进行删除的。

因此,MySQL 记录锁+间隙锁可以防止删除操作而导致的幻读问题。

加锁分析

问题来了,A 事务在执行 select … for update 语句时,具体加了什么锁呢?

我们可以通过 select * from performance_schema.data_locks\G; 这条语句,查看事务执行 SQL 过程中加了什么锁。

输出的内容很多,共有 11 行信息,我删减了一些不重要的信息:
在这里插入图片描述

从上面输出的信息可以看到,共加了两种不同粒度的锁,分别是:

  • 表锁(LOCK_TYPE: TABLE):X 类型的意向锁;
  • 行锁(LOCK_TYPE: RECORD):X 类型的 next-key 锁;

这里我们重点关注「行锁」,图中 LOCK_TYPE 中的 RECORD 表示行级锁,而不是记录锁的意思:

  • 如果 LOCK_MODE 为 X,说明是 next-key 锁;
  • 如果 LOCK_MODE 为 X, REC_NOT_GAP,说明是记录锁;
  • 如果 LOCK_MODE 为 X, GAP,说明是间隙锁;

然后通过 LOCK_DATA 信息,可以确认 next-key 锁的范围,具体怎么确定呢?

  • 根据我的经验,如果 LOCK_MODE 是 next-key 锁或者间隙锁,那么 LOCK_DATA 就表示锁的范围最右值,而锁范围的最左值为 LOCK_DATA 的上一条记录的

因此,此时事务 A 在主键索引(INDEX_NAME : PRIMARY)上加了 10 个 next-key 锁,如下:

  • X 型的 next-key 锁,范围:(-∞, 1]
  • X 型的 next-key 锁,范围:(1, 2]
  • X 型的 next-key 锁,范围:(2, 3]
  • X 型的 next-key 锁,范围:(3, 4]
  • X 型的 next-key 锁,范围:(4, 5]
  • X 型的 next-key 锁,范围:(5, 6]
  • X 型的 next-key 锁,范围:(6, 7]
  • X 型的 next-key 锁,范围:(7, 8]
  • X 型的 next-key 锁,范围:(8, 9]
  • X 型的 next-key 锁,范围:(9, +∞]

这相当于把整个表给锁住了,其他事务在对该表进行增、删、改操作的时候都会被阻塞。

只有在事务 A 提交了事务,事务 A 执行过程中产生的锁才会被释放。

为什么只是查询年龄 20 岁以上行记录,而把整个表给锁住了呢?

这是因为事务 A 的这条查询语句是全表扫描,锁是在遍历索引的时候加上的,并不是针对输出的结果加锁。

在这里插入图片描述

因此,在线上在执行 update、delete、select … for update 等具有加锁性质的语句,一定要检查语句是否走了索引,如果是全表扫描的话,会对每一个索引加 next-key 锁,相当于把整个表锁住了,这是挺严重的问题。

如果对 age 建立索引,事务 A 这条查询会加什么锁呢?

接下来,我对 age 字段建立索引,然后再执行这条查询语句:

在这里插入图片描述

接下来,继续通过 select * from performance_schema.data_locks\G; 这条语句,查看事务执行 SQL 过程中加了什么锁。

具体的信息,我就不打印了,我直接说结论吧。

因为表中有两个索引,分别是主键索引和 age 索引,所以会分别对这两个索引加锁。二级索引树加next-key lock,主键索引树加record lock

主键索引会加如下的锁:

  • X 型的记录锁,锁住 id = 2 的记录;
  • X 型的记录锁,锁住 id = 3 的记录;
  • X 型的记录锁,锁住 id = 5 的记录;
  • X 型的记录锁,锁住 id = 6 的记录;
  • X 型的记录锁,锁住 id = 7 的记录;
  • X 型的记录锁,锁住 id = 8 的记录;

分析 age 索引加锁的范围时,要先对 age 字段进行排序。

在这里插入图片描述

age 索引加的锁:

  • X 型的 next-key lock,锁住 age 范围 (19, 21] 的记录;
  • X 型的 next-key lock,锁住 age 范围 (21, 21] 的记录;
  • X 型的 next-key lock,锁住 age 范围 (21, 23] 的记录;
  • X 型的 next-key lock,锁住 age 范围 (23, 23] 的记录;
  • X 型的 next-key lock,锁住 age 范围 (23, 39] 的记录;
  • X 型的 next-key lock,锁住 age 范围 (39, 43] 的记录;
  • X 型的 next-key lock,锁住 age 范围 (43, +∞] 的记录;

化简一下,age 索引 next-key 锁的范围是 (19, +∞]

可以看到,对 age 字段建立了索引后,查询语句是索引查询,并不会全表扫描,因此不会把整张表给锁住

在这里插入图片描述

总结一下,在对 age 字段建立索引后,事务 A 在执行下面这条查询语句后,主键索引和 age 索引会加下图中的锁。

在这里插入图片描述

事务 A 加上锁后,事务 B、C、D、E 在执行以下语句都会被阻塞。

在这里插入图片描述

总结

在 MySQL 的可重复读隔离级别下,针对当前读的语句会对索引加记录锁+间隙锁,这样可以避免其他事务执行增、删、改时导致幻读的问题。

有一点要注意的是,在执行 update、delete、select … for update 等具有加锁性质的语句,一定要检查语句是否走了索引,如果是全表扫描的话,会对每一个索引加 next-key 锁,相当于把整个表锁住了,这是挺严重的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/357432.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用Fairseq进行Bart预训练

文章目录前言环境流程介绍数据部分分词部分预处理部分训练部分遇到的问题问题1可能遇到的问题问题1问题2前言 本文是使用 fairseq 做 Bart 预训练任务的踩坑记录huggingface没有提供 Bart 预训练的代码 facebookresearch/fairseq: Facebook AI Research Sequence-to-Sequence…

字符串匹配 - 文本预处理:后缀树(Suffix Tree)

上述字符串匹配算法(朴素的字符串匹配算法, KMP 算法, Boyer-Moore算法)均是通过对模式(Pattern)字符串进行预处理的方式来加快搜索速度。对 Pattern 进行预处理的最优复杂度为 O(m),其中 m 为 Pattern 字符串的长度。那么,有没有…

windows环境下,vue启动项目后打开chrome浏览器

前言:关于vue启动后打开chrome浏览器,我查了很多资料,方案如下: 1、增加环境变量BROWSER为chrome(试了没效果) 2、设置系统的默认浏览器为chrome(应该可以,但没试;因为…

有序表的应用:[Leetcode 327] 区间和的个数

一、题目 1、题目描述 给你一个整数数组 nums 以及两个整数 lower 和 upper 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 区间和的个数 。 区间和 S(i, j)表示在 nums 中,位置从 i 到 j 的元素之和&…

基于jsplumb构建的流程设计器

项目背景 最近在准备开发工作流引擎相关模块,完成表结构设计后开始着手流程设计器的技术选型,调研了众多开源项目后决定基于jsplumb.js开源库进行自研开发,保证定制化的便捷性,相关效果图及项目地址如下 项目地址:ht…

进程切换-

实验课之前有一些问题 中断机制 第一个问题: interrupt的两个状态源头: 外中断多由随机中断(异步中断)造成,如鼠标点击,键盘输入; 内终端多由故障终端:程序运行异常,硬件…

使用PyQtGraph 自定义绘图

Python 的主要优势之一是探索性数据科学和可视化生态体系。一般的工具链为Pandas、numpy、sklearn 进行数据分析和使用matplotlib进行绘图。 但是如果需要自己自定义一个个性化的图形界面工具,则可能不是很合适。为了实现这种需求,可以使用PyQt构建GUI应…

【进阶篇】线程的硬件基础

文章目录高速缓存缓存一致性协议写缓冲区和无效化队列高速缓存 简介 高速缓存是主内存与处理器之间的硬件,其容量小于主存,但存取速率远高于主存。因此处理器在执行读写操作时,可直接和高速缓存交互,提高响应速度。 我们常见的变…

2049. 统计最高分的节点数目

2049. 统计最高分的节点数目题目算法设计:深度优先搜索题目 传送门:https://leetcode.cn/problems/count-nodes-with-the-highest-score/ 算法设计:深度优先搜索 这题的核心是计算分数。 一个节点的分数 左子树节点数 右子树节点数 除自…

【Opencv 系列】 第4章 直方图

文章目录[TOC](文章目录)前言1、直方图的定义、意义、特征2、直方图:2.1 灰度直方图2.2 彩色直方图前言 1、直方图的定义、意义、特征 在统计学中,直方图是一种对数据分布情况的图形表示,是一种二维统计图表,他的两个坐标分别是统…

docker安装配置镜像加速器-拉取创建Mysql容器示例

List item docker 常见命令大全docker安装docker拉取创建Mysql容器docker 安装 1、安装链接:https://blog.csdn.net/BThinker/article/details/123358697 ; 2、安装完成需要配置docker镜像加速器 3、docker 镜像加速器推荐使用阿里云的: 编…

硬件学习 软件Cadence day04 PCB 封装绘制

1.文章内容: 1. 贴片式电容 PCB 封装绘制 (型号 c0603 ) 2. 贴片式电阻 PCB 封装绘制 (型号 r0603 ) 3. 安规式电容 PCB 封装绘制 (这个就是 有一个电容,插入一个搞好的孔里面 …

社区宠物诊所管理系统

目录第一章概述 PAGEREF _Toc4474 \h 21.1引言 PAGEREF _Toc29664 \h 31.2开发背景 PAGEREF _Toc3873 \h 3第二章系统总体结构及开发 PAGEREF _Toc19895 \h 32.1系统的总体设计 PAGEREF _Toc6615 \h 32.2开发运行环境 PAGEREF _Toc13054 \h 3第三章数据库设计 PAGEREF _Toc2852…

Prometheus 告警机制介绍及命令解读

本文您将了解到Prometheus 告警模块Alertmanager的架构介绍、核心概念、命令解析和AMTool的基本使用。 Prometheus的告警模块并不存在于Prometheus中,而是 以独立项目Alertmanager存在。Prometheus服务器定义告警规则,这些规则将触发警报,将警报发送到Alertmanager。Alertma…

DaVinci 偏好设置:系统 - 解码选项

偏好设置 - 系统/解码选项Preferences - System/Decode Options解码选项Decode Options使用 GPU 进行 Blackmagic RAW 解码Use GPU for Blackmagic RAW decode允许使用 GPU 来加速 Blackmagic RAW(BRAW)媒体的解码。使用硬件加速解码 H.264/H.265Decode …

谁再用Double定义存储计算金额,我劈了他

不是三婶儿偏执,非要吐槽。家人们,咱就是说,按照基操逻辑谁会把严格金额计算相关的数据使用double类型呢… “我以为吕布已经够勇猛了,这是谁的部下?” 前几天,一同事让帮忙写段代码。内容比较常规&#xf…

Unity之ASE实现边缘光效果

一.前言 今天来实现一个简单的边缘光效果,可以应用与物体表面的一种荧光外溢的效果,特别是用在人的身体表面,会让人的皮肤更细腻,更好看。 物体上效果如下: 人体表面效果如下: 我们可以看到,这种人体表面…

Redis的整合和使用

引入依赖 <!--整合redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>添加配置 spring.redis.host127.0.0.1 spring.redis.port6379 spring.…

算法训练营 day49 动态规划 爬楼梯 (进阶)零钱兑换 完全平方数

算法训练营 day49 动态规划 爬楼梯 &#xff08;进阶&#xff09;零钱兑换 完全平方数 爬楼梯 &#xff08;进阶&#xff09; 70. 爬楼梯 - 力扣&#xff08;LeetCode&#xff09; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同…

PPO(proximal policy optimization)算法

博客写到一半发现有篇讲的很清楚&#xff0c;直接化缘了 https://www.jianshu.com/p/9f113adc0c50 Policy gradient 强化学习的目标&#xff1a;学习到一个策略πθ(a∣s)\pi\theta(a|s)πθ(a∣s)来最大化期望回报。 一种直接的方法就是在策略空间中直接搜索来得到最优策略&…