MySQL之幻读问题

news2024/11/26 10:45:00

MySQL之幻读问题

导读

在进入今天的主题之前必须先了解事务的四大特性ACID、MVCC、事务隔离级别(具体的自行查询),其中I(Isolation)隔离性所产生的问题涉及到的事务隔离分为不同级别,包括读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable)

  • 读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。
  • 读提交:一个事务提交之后,它做的变更才会被其他事务看到。
  • 可重复读: 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
  • 串行化: 是对于同一行记录,写会加“写锁”,读会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

SQL标准中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:

隔离级别脏读不可重复读幻读
读未提交可能可能可能
读提交不可能可能可能
可重复读不可能不可能可能
串行化不可能不可能不可能

InnoDB默认使用的隔离级别就是可重复读(RR),这次着重说的就是RR级别下的幻读问题。

幻读定义

幻读MYSQL官方叫法是Phantom Rows,意为幻影行或幽灵行,请看官方定义:
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.
翻译出来就是:当同一查询在不同时间产生不同的行集时,就会在事务中出现所谓的幻影问题。例如,同一个事务中,同一个查询语句执行两次,第二次执行比第一次执行查出的结果多了一行,这一行就是幽灵行。

举例:第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的多条记录。同时,第二个事务向表中插入一条新记录。那么,之后就会发生操作第一个事务的用户发现表中修改的数据记录数与原先不一致,就好象发生了幻觉一样。

幻读与不可重复读的区别

幻读和不可重复读着两个概念一不注意就可能混淆了,从官方的定义来看,幻读侧重的是多行记录,属于记录数的变化。而不可重复读侧重的是单挑记录的数据变化。
区分这两者的原因:幻读问题的处理需要使用间隙锁,而不可重复读的处理只需要锁住行记录。

案例

CREATE TABLE `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `tag` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `age` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci


INSERT INTO `test`.`tag` (`id`, `tag`, `age`) VALUES (1, 'one', 20), (2, 'two', 20);

模拟并发事务时的幻读场景,两个Session的操作顺序如下:

时间线sessionAsessionB
1set global transaction isolation level repeatable read;
begin;
select * from tag where age = 20;
2set global transaction isolation level repeatable read;
begin;
insert into tag VALUES(3, ‘three’, 20);
3// 验证B事务没提交能否被A事务拿到数据
select * from tag where age = 20;
4commit;
5// 验证B事务提交后A事务能否拿到最新数据
select * from tag where age = 20;

update tag set tag = ‘test1’ where age = 20;

// 更新操作,导致select结果拿到了B提交的数据
select * from tag where age = 20;
commit;

step1 SessionA


mysql> set global transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> use test;

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from tag where age = 20;
+----+-----+-----+
| id | tag | age |
+----+-----+-----+
|  1 | one |  20 |
|  2 | two |  20 |
+----+-----+-----+
2 rows in set (0.00 sec)

step2 同一时刻SessionB所在事务向同一个表中插入满足条件范围的数据


mysql> set global transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> use test;

mysql> begin;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into tag VALUES(3, 'three', 20);
Query OK, 1 row affected (0.00 sec)

step3 SessionA 验证:再次查询验证B事务写入成功但是未提交事务的数据能否被A事务获取


mysql> select * from tag where age = 20;
+----+-----+-----+
| id | tag | age |
+----+-----+-----+
|  1 | one |  20 |
|  2 | two |  20 |
+----+-----+-----+
2 rows in set (0.00 sec)

step4 SessionB 提交事务


mysql> commit;
Query OK, 0 rows affected (0.01 sec)

step5 SessionA

  • 验证:再次查询验证B事务提交后,A事务能否获取到最新的符合条件的数据
  • 更新满足条件的数据,发现更新的数据行与查询的不一致
  • 再次查询满足条件数据,发现“产生幻觉”

mysql> select * from tag where age = 20;
+----+-----+-----+
| id | tag | age |
+----+-----+-----+
|  1 | one |  20 |
|  2 | two |  20 |
+----+-----+-----+
2 rows in set (0.00 sec)

mysql> update tag set tag = 'test1' where age = 20;
Query OK, 3 rows affected (0.00 sec)
Rows matched: 3  Changed: 3  Warnings: 0

mysql> select * from tag where age = 20;
+----+-------+-----+
| id | tag   | age |
+----+-------+-----+
|  1 | test1 |  20 |
|  2 | test1 |  20 |
|  3 | test1 |  20 |
+----+-------+-----+
3 rows in set (0.01 sec)

以上就是模拟幻读场景的sql操作(ps:我只是简单模拟两个事务交互操作,真实的事务中的写入操作不少于2条)。

幻觉产生的原因

由以上案例,定位出产生"幻觉"的地方是在update操作执行后。为什么update之后的select出来的结果是最新的已提交的数据?

这里先讲下两个涉及的概念

  • 快照读:可重复读隔离级别下,普通的查询属于快照读,是不会看到别的事务插入的数据。
    如:select * from table where ?;

  • 当前读:顾名思义就是读到当前最新的数据,幻读只有在“当前读”的情况下才会出现。加锁(锁的内容有机会再分享)、插入、更新、删除操作都属于当前读


如:
	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 ?;

回到原因:是前面的update语句执行,会将当前记录存储的事务信息更新为当前的事务,而当前事务所做的任何更新,对本事务所有select查询都变为可见,因此最后查询到的结果是update更新了所有符合条件的当前事务的数据记录。(ps:此处是对mysql的innoDB源码的分析得出的结论,下次有机会再拿出来剖析)

问题处理

分析:update的快照读操作之后本事务select查询出来的是当前最新数据,但是因为业务我们又不能放弃update,那就只能从另一个事务的insert入手,有没有让事务A的更新操作提交之前阻止事务B插入数据的办法?或者说有没有能让本事务从头到尾select查询的记录一直是最新且不受其他事务影响,直到事务A提交?

  • 行锁:select … where … for update(当前读)是将所有符合where条件的行加上行锁(innodb内会对该索引(where条件)加锁,即使当前不存在此数据)。
  • 间隙锁(Gap Lock):顾名思义,间隙锁,锁的就是两个值之间的空隙,比如上面的案例数据,初始化插入2条记录,这就产生了2个间隙,3个间隙锁。区间范围:(-⚮,1) (1,2) (2,+⚮)。

如下图:GapLock.jpg

  • 临键锁(Next-key Lock):可以简单理解为行锁+Gap锁,是行锁+间隙锁的组合,他的锁范围既包含索引记录,也包含索引区间。左开右闭的区间范围:(-⚮,1] (1,2] (2,+⚮]。

注意:临键锁主要是为了避免幻读。如果把事务的隔离级别降级为RC,临键锁则会失效。

行锁只能锁住满足条件的行记录,但是新插入记录,更新的是记录之间的“间隙”。因此,为了解决幻读问题,innoDB只好引入新的锁,也就是间隙锁。
根据上面的案例数据,当你执行 select * from tag where age = 20 for update 的时候,就不止是给数据库中已有的2条数据加上了行锁,同时还加了3个间隙锁。

这样就确保无法再插入新的记录,事务B在insert数据时,因为ID大于2,被间隙锁(2,+⚮)锁住,无法插入。
不仅给满足条件的记录加上了行锁,还给行两边的空隙加上了间隙锁。MySQL将行锁+间隙锁组合统称为临键锁,通过临键锁解决了幻读问题。

现在在事务A中查询时使用for update,在innoDB中会对索引加锁(即使当前数据不存在),于是事务B的insert会被阻塞直到事务Acommit成功后再执行。

step1 SessionA给记录加行锁


mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from tag where age = 20 for update;
+----+-----+-----+
| id | tag | age |
+----+-----+-----+
|  1 | one |  20 |
|  2 | two |  20 |
+----+-----+-----+
2 rows in set (0.00 sec)

step2 SessionB


mysql> begin;
Query OK, 0 rows affected (0.00 sec)

// 此时插入会报错提示锁等待
mysql> insert into tag values(3, 'test', 20);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

step3 SessionA执行对应逻辑操作后提交事务


mysql> update tag set tag='test1' where age = 20;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0

mysql> select * from tag where age = 20 for update;
+----+-------+-----+
| id | tag   | age |
+----+-------+-----+
|  1 | test1 |  20 |
|  2 | test1 |  20 |
+----+-------+-----+
2 rows in set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.01 sec)

step4 SessionB此时可以成功插入数据了


mysql> insert into tag values(3, 'test', 20);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from tag;
+----+-------+-----+
| id | tag   | age |
+----+-------+-----+
|  1 | test1 |  20 |
|  2 | test1 |  20 |
|  3 | test  |  20 |
+----+-------+-----+
3 rows in set (0.00 sec)

MySQL事务默认隔离级别可重复读(RR),是事务安全与性能的折中,了解"幻读"后,便可以根据业务需求决定是否需要防止幻读。

PS:还有一种解决方案,但是不推荐使用,性能较差。

将事务隔离级别设置为串行化(serializable),串行化是悲观认为幻读肯定会发生,所以会隐式的对本事务所需资源加排它锁,让本事务保持安全,其他事务访问有关资源都会被阻塞等待,性能太差了。

我是六涛sheliutao,文章编写总结不易,转载注明出处,喜欢本篇文章的小伙伴欢迎点赞、关注,有问题可以评论区留言或者私信我,相互交流!!!

参考

  • MySQL-Phantom Rows

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

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

相关文章

python+pytest接口自动化(7)-cookie绕过登录(保持登录状态)

在编写接口自动化测试用例或其他脚本的过程中,经常会遇到需要绕过用户名/密码或验证码登录,去请求接口的情况,一是因为有时验证码会比较复杂,比如有些图形验证码,难以通过接口的方式去处理;再者&#xff0c…

Java多线程——线程安全、synchronized、volatile关键字以及多线程案例

文章目录前言一、线程安全—多线程不可避免的风险!1、线程不安全的示例2、线程不安全的原因二、synchronized关键字1.synchronized的特性1)互斥2)刷新内存3)可重入2、synchronized使用示例3、Java标准库中的线程安全类三、volatil…

【巨人的肩膀】JAVA面试总结(四)

💪、JVM 目录💪、JVM1、说一下JVM的主要组成部分及其作用2、什么是JVM内存结构(谈谈对运行时数据区的理解)3、堆和栈的区别是什么4、堆中存什么?栈中存什么?5、为什么不把基本类型放堆中呢?6、为…

理论上BI软件适配任何行业,但为什么有些行业做不了?

BI商业智能是一种通用的数据类技术解决方案。理论上来说,BI商业智能的核心是数据。只要企业有数据累积,就可以在BI软件上展开一系列的数据开发,获取决策所需的数据依据。但在现实中,却会发现有些BI软件对特定的行业束手无策&#…

【站外SEO】如何利用外部链接来提高你的网站排名

随着互联网的快速发展,越来越多的企业开始注重SEO优化,以提升自己的网站排名,增加流量和曝光度。 而站外SEO作为SEO的重要组成部分,对于提升网站排名具有不可忽视的作用。 站外SEO主要是通过外部链接来提高网站的排名。而GPB外链…

4EVERLAND 的 IPFS Pinning 服务:4EVER Pin

我们很高兴地宣布 4EVERLAND Storage 的一个令人兴奋的补充,即 4EVER Pin。什么是 4EVER Pin?您可能已经知道星际文件系统或IPFS是一个分布式存储网络,来自世界各地的计算机组成节点共享数据。通常,在IPFS中获取一条数据时&#x…

3/3操作系统作业

目录 1.前趋图和程序执行 (1)前驱图 (2)程序的顺序执行 (3)程序的并发执行 2.进程的描述 (1)进程的定义与特征 ​编辑​编辑(2)进程控制块​编辑 &…

ESP32设备驱动-GUVA-S12SD紫外线检测传感器驱动

GUVA-S12SD紫外线检测传感器驱动 文章目录 GUVA-S12SD紫外线检测传感器驱动1、GUVA-S12SD介绍2、硬件准备3、软件准备4、驱动实现1、GUVA-S12SD介绍 GUVA-S12SD 紫外线传感器芯片适用于检测太阳光中的紫外线辐射。 它可用于任何需要监控紫外线量的应用,并且可以简单地连接到任…

DebugView在Vs + Qt 应用程序中的使用

1.准备头文件mylog.h #pragma once #ifndef _MYLOG_H_ #define _MYLOG_H_#include <windows.h> #include <tchar.h>#define DP0(fmt) {TCHAR sOut[256];_stprintf_s(sOut,_T(fmt));OutputDebugString(sOut);} #define DP1(fmt,var) {TCHAR sOut[256];_stprintf_s(…

【Java】CompletableFuture 并发顺序调度

前言 Java CompletableFuture 提供了一种异步编程的方式&#xff0c;可以在一个线程中执行长时间的任务&#xff0c;而不会堵塞主线程。 和Future相比&#xff0c;CompletableFuture不仅实现了Future接口&#xff0c;也实现了 CompletionStage接口。Future接口不用多说&#…

react renderProps学习记录

react renderProps学习记录1.引入2.改一下呢3.再改一下呢4.总结一下如何向组件内部动态传入带内容的结构(标签)?children propsrender props1.引入 上代码&#xff1a; import React, { Component } from react import ./index.css export default class Parent extends Com…

【云原生】K8S调度约束

一、调度约束 Kubernetes 是通过 List-Watch&#xff08;监控&#xff09; 的机制进行每个组件的协作&#xff0c;保持数据同步的&#xff0c;每个组件之间的设计实现了解耦。 用户是通过 kubectl 根据配置文件&#xff0c;向 APIServer 发送命令&#xff0c;在 Node 节点上面…

ANR系列(二)——ANR监听方案之SyncBarrier

前言 在项目中经常遇到了手机假死问题&#xff0c;无规律的偶现问题&#xff0c;大量频繁随机操作后&#xff0c;便会出现假死&#xff0c;整个应用无法操作&#xff0c;不会响应事件&#xff0c;会发生各种奇怪的ANR&#xff0c;且trace不固定。而SyncBarrier是其中的罪魁祸首…

RabbitMq(具体怎么用,看这一篇即可)

RabbitMq汇总1.RabbitMq的传统实现方式2.SpringAMQP简化RabbitMq开发2.1 基本消息队列&#xff08;BasicQueue&#xff09;2.2 工作消息队列&#xff08;WorkQueue&#xff09;2.3 发布订阅 -- 广播&#xff08;Fanout&#xff09;2.4 发布订阅 -- 路由&#xff08;Direct&…

2024级浙江大学MBA提面申请流程参考

近年来浙大MBA项目的招生一直都有提前批面试的环节&#xff0c;而且每年在申请政策方面也会做出一些微调&#xff0c;但大的方面不会做调整&#xff0c;2024年MBA提面申请即将开始&#xff0c;对此杭州达立易考教育结合2023年的情况为大家梳理出来基本的申请流程和批次参考&…

【Spring Boot】Spring Boot以Repository方式整合Redis

1 简介 Redis是高性能的NoSQL数据库&#xff0c;经常作为缓存流行于各大互联网架构中。本文将介绍如何在Springboot中整合Spring Data Redis&#xff0c;使用Repository的方式操作。 代码结构如下&#xff1a; 2 整合过程 2.1 安装Redis数据库 为了节省时间&#xff0c;就直…

为什么很多计算机专业大学生毕业后还会参加培训?

基于IT互联网行业越来越卷的现状&#xff0c;就算是科班出身&#xff0c;很多也是达不到用人单位的要求。面对这样的现实情况&#xff0c;有的同学会选择继续深造&#xff0c;比如考个研&#xff0c;去年考研人数457万人次&#xff0c;可见越来越的同学是倾向考研提升学历来达到…

管道。环境变量和常用命令

文章目录管道与文件重定向的区别举例环境变量使用自己编写的程序像命令符一样常用命令grepagwctree . -acutsortmorehistory管道 管道类似于重定向&#xff0c;但是又不太一样&#xff0c;管道可以连接好几个&#xff0c;把第一个的输出当成第二个的输入&#xff0c;第二个的输…

实践Spring5 响应式编程框架WebFlux

WebFlux 以 Reactor 库为基础, 基于异步和事件驱动&#xff0c;可以让我们在不扩充硬件资源的前提下&#xff0c;提升系统的吞吐量和伸缩性。一、什么是 Spring WebFlux了解 WebFlux ,首先了解下什么是 Reactive Streams。Reactive Streams 是 JVM 中面向流的库标准和规范&…

论文推荐:ScoreGrad,基于能量模型的时间序列预测

能量模型&#xff08;Energy-based model&#xff09;是一种以自监督方式执行的生成式模型&#xff0c;近年来受到了很多关注。本文将介绍ScoreGrad&#xff1a;基于连续能量生成模型的多变量概率时间序列预测。如果你对时间序列预测感兴趣&#xff0c;推荐继续阅读本文。 为什…