Innodb的RR到底有没有解决幻读?

2024-04-28 11:04

本文主要是介绍Innodb的RR到底有没有解决幻读?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在InnoDB中,Repeatable Read(重复读)隔离级别通过间隙锁和MVCC机制解决了大部分的幻读问题,但并非所有幻读都能被解决。要彻底解决幻读,需要使用Serializable(可串行化)隔离级别。

在Repeatable Read隔离级别下,通过间隙锁解决了部分当前读导致的幻读问题。通过添加间隙锁来锁定记录之间的间隙,以防止新数据的插入。

在Repeatable Read隔离级别下,通过MVCC机制解决了快照读导致的幻读问题。在该隔离级别下,进行快照读时仅在第一次进行数据查询,随后直接读取快照,因此不会发生幻读。

然而,若两个事务操作如下:事务1首先进行快照读,然后事务2插入一条记录并提交,在事务1之后通过更新操作这个新插入的记录,这样可以成功更新,这就是幻读的一种情况。

另外一个场景是,若两个事务的顺序为:事务1先进行快照读,接着事务2插入了一条记录并提交,在事务1进行当前读后,再次进行快照读也会导致幻读的发生。

MVCC解决幻读

MVCC,即多版本并发控制(Multiversion Concurrency Control),类似于数据库锁,是一种并发控制的解决方案。它主要用于解决读-写并发的情况。

我们了解,在MVCC中存在两种读取方式:快照读和当前读

快照读指的是读取快照数据,即在生成快照的那一瞬间的数据。例如,通常情况下我们使用的普通SELECT语句在不加锁的情况下就是一种快照读。

在可重复读(RC)中,每次读取都会重新生成一个快照,始终读取行的最新版本。在可重复读(RR)中,快照会在事务第一次执行SELECT语句时生成,只有在本事务中对数据进行更改才会更新快照。

因此,在RR隔离级别下,同一事务中的多次查询不会检索到其他事务的更改内容,因此能够解决幻读问题。

若我们将事务隔离级别设置为RR,由于MVCC的机制,就可以解决幻读问题。

有这样一张表:

CREATE TABLE users (id INT UNSIGNED AUTO_INCREMENT,gmt_create DATETIME NOT NULL,age INT NOT NULL,name VARCHAR(16) NOT NULL,PRIMARY KEY (id)
) ENGINE=InnoDB;INSERT INTO users(gmt_create,age,name) values(now(),18,'Paidaxing');
INSERT INTO users(gmt_create,age,name) values(now(),28,'Paidaxing2023');
INSERT INTO users(gmt_create,age,name) values(now(),38,'Paidaxing666');

执行如下事务时序:

事务1
SET session TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT * FROM users WHERE AGE > 10 AND AGE <30; image.png
BEGIN;
INSERT INTO users(gmt_create, age, name) values(now(), 20, ‘Paidaxing999’); image.png
COMMIT;
SELECT * FROM users WHERE AGE > 10 AND AGE < 30; image.png

可以观察到,在同一个事务中,两次查询的结果是相同的。在可重复读(RR)级别下,由于采用了快照读,第二次查询实际上是读取的快照数据。

间隙锁与幻读

我们已经讨论了MVCC如何解决了可重复读(RR)级别下的快照读造成的幻读问题,那么在当前读取(READ COMMITTED)下,如何解决幻读问题呢?

当前读取即读取最新数据,因此,锁定的SELECT语句,或者进行数据的插入、删除、更新都属于当前读取操作,例如:

SELECT * FROM xx_table LOCK IN SHARE MODE;SELECT * FROM xx_table FOR UPDATE;INSERT INTO xx_table ...DELETE FROM xx_table ...UPDATE xx_table ...

举一个下面的例子:

事务1事务2
SET session TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT * FROM users WHERE AGE > 10 AND AGE < 30 for update; image.png
BEGIN;
INSERT INTO users(gmt_create, age, name) values(now(), 20, ‘Paidaxing999’);
阻塞 image.png

在可重复读(RR)级别下,当我们使用SELECT … FOR UPDATE时,会进行锁定操作。这不仅会对行记录进行加锁,还会对记录之间的间隙进行加锁,这就是所谓的间隙锁。

由于记录之间的间隙被锁定,事务2的插入操作被阻塞,直到事务1释放锁才得以成功执行。

由于事务2无法成功插入数据,因此幻读现象得以避免。因此,在可重复读(RR)级别中,通过引入间隙锁的方式,成功规避了幻读现象的发生。

解决不了的幻读

前面我们讨论了快照读(无锁查询)和当前读(有锁查询)是如何解决幻读问题的。然而,上面提到的例子并非幻读的全部情况。

我们知道MVCC只能解决快照读导致的幻读问题,那么如果一个事务中发生了当前读,在另一个事务插入数据前未加间隙锁,会发生什么呢?

接下来,我们稍作修改上面的SQL代码,采用当前读方式来查询数据:

事务1事务2
SET session TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT * FROM users WHERE AGE > 10 AND AGE <30; image.png
BEGIN;
INSERT INTO users(gmt_create, age, name) values(now(), 20, ‘Paidaxing999’); image.png
COMMIT;
SELECT * FROM users WHERE AGE > 10 AND AGE < 30; image.png
SELECT * FROM users WHERE AGE > 10 AND AGE < 30 for update; image.png

在上面的例子中,在事务1中,我们并未在事务刚启动时立即加锁,而是进行了一次普通的查询,随后事务2成功插入数据后,事务1再进行了两次查询。

我们观察到,事务1后两次查询的结果完全不同。在没有加锁的情况下,即快照读时,读取的数据与第一次查询结果相同,从而避免了幻读现象。但第二次查询执行了锁定操作,即当前读,因此读取到的数据中包含了其他事务提交的数据,导致了幻读的发生。

倘若您理解了上述例子以及当前读的概念,您将很容易意识到,下面的这个案例事实上也会导致幻读的发生:

事务1事务2
SET session TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT * FROM users WHERE AGE > 10 AND AGE <30; image.png
BEGIN;
INSERT INTO users(gmt_create, age, name) values(now(), 20, ‘Paidaxing999’); image.png
COMMIT;
SELECT * FROM users WHERE AGE > 10 AND AGE <30; image.png
UPDATE users set name = “Paidaxing888” where age = 20; image.png
SELECT * FROM users WHERE AGE > 10 AND AGE <30; image.png

这里产生幻读的原因和前面的例子实际上是相同的。即,MVCC只能解决快照读中的幻读问题,而对于当前读(例如 SELECT FOR UPDATE、UPDATE、DELETE 等操作)仍会导致幻读的产生。在同一个事务中同时进行快照读和当前读操作时,将导致幻读的发生。

UPDATE 语句也属于当前读操作,因此它有可能读取到其他事务提交的结果。

为何事务1最后一次查询和倒数第二次查询的结果会不同呢?

原因在于根据快照读的定义,在可重复读级别下,如果在本事务中发生了数据修改,将会更新快照数据,因此最后一次查询的结果也会相应地发生变化。

如何避免幻读

了解了幻读产生的情境以及无法解决的几种情况后,让我们总结一下如何解决幻读的问题。

首先,若欲彻底解决幻读问题,在 InnoDB 中唯一可选的隔离级别是 Serializable(可串行化)级别。

image.png

图源:MySQL 8.0 参考手册

若希望在一定程度上解决或避免幻读,可考虑使用可重复读(RR)隔离级别,但读提交(RC)和读未提交(RU)级别肯定不可行。

在可重复读级别中,尽量使用快照读(无锁查询),这样不仅可以减少锁冲突、提高并发度,还能避免幻读问题的发生。

在高并发场景中若必须加锁,应在事务开始时立即加锁,这将引入间隙锁,有效地避免幻读。

然而,值得注意的是,间隙锁是引发死锁的重要因素,因此在使用时需要谨慎对待。

这篇关于Innodb的RR到底有没有解决幻读?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/943093

相关文章

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

mysql出现ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost‘ (10061)的解决方法

《mysql出现ERROR2003(HY000):Can‘tconnecttoMySQLserveron‘localhost‘(10061)的解决方法》本文主要介绍了mysql出现... 目录前言:第一步:第二步:第三步:总结:前言:当你想通过命令窗口想打开mysql时候发现提http://www.cpp

SpringBoot启动报错的11个高频问题排查与解决终极指南

《SpringBoot启动报错的11个高频问题排查与解决终极指南》这篇文章主要为大家详细介绍了SpringBoot启动报错的11个高频问题的排查与解决,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一... 目录1. 依赖冲突:NoSuchMethodError 的终极解法2. Bean注入失败:No qu

springboot报错Invalid bound statement (not found)的解决

《springboot报错Invalidboundstatement(notfound)的解决》本文主要介绍了springboot报错Invalidboundstatement(not... 目录一. 问题描述二.解决问题三. 添加配置项 四.其他的解决方案4.1 Mapper 接口与 XML 文件不匹配

Python中ModuleNotFoundError: No module named ‘timm’的错误解决

《Python中ModuleNotFoundError:Nomodulenamed‘timm’的错误解决》本文主要介绍了Python中ModuleNotFoundError:Nomodulen... 目录一、引言二、错误原因分析三、解决办法1.安装timm模块2. 检查python环境3. 解决安装路径问题

如何解决mysql出现Incorrect string value for column ‘表项‘ at row 1错误问题

《如何解决mysql出现Incorrectstringvalueforcolumn‘表项‘atrow1错误问题》:本文主要介绍如何解决mysql出现Incorrectstringv... 目录mysql出现Incorrect string value for column ‘表项‘ at row 1错误报错

如何解决Spring MVC中响应乱码问题

《如何解决SpringMVC中响应乱码问题》:本文主要介绍如何解决SpringMVC中响应乱码问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC最新响应中乱码解决方式以前的解决办法这是比较通用的一种方法总结Spring MVC最新响应中乱码解

Java报NoClassDefFoundError异常的原因及解决

《Java报NoClassDefFoundError异常的原因及解决》在Java开发过程中,java.lang.NoClassDefFoundError是一个令人头疼的运行时错误,本文将深入探讨这一问... 目录一、问题分析二、报错原因三、解决思路四、常见场景及原因五、深入解决思路六、预http://www

pip无法安装osgeo失败的问题解决

《pip无法安装osgeo失败的问题解决》本文主要介绍了pip无法安装osgeo失败的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 进入官方提供的扩展包下载网站寻找版本适配的whl文件注意:要选择cp(python版本)和你py

SpringBoot项目启动报错"找不到或无法加载主类"的解决方法

《SpringBoot项目启动报错找不到或无法加载主类的解决方法》在使用IntelliJIDEA开发基于SpringBoot框架的Java程序时,可能会出现找不到或无法加载主类com.example.... 目录一、问题描述二、排查过程三、解决方案一、问题描述在使用 IntelliJ IDEA 开发基于