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;

BEGIN;

INSERT INTO users(gmt_create, age, name) values(now(), 20, 'Paidaxing999');

COMMIT;

SELECT * FROM users WHERE AGE > 10 AND AGE < 30;

可以观察到,在同一个事务中,两次查问的结果是相反的。在可重复读(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;

BEGIN;

INSERT INTO users(gmt_create, age, name) values(now(), 20, 'Paidaxing999');

阻塞

在可重复读(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;

BEGIN;

INSERT INTO users(gmt_create, age, name) values(now(), 20, 'Paidaxing999');

COMMIT;

SELECT * FROM users WHERE AGE > 10 AND AGE < 30;

SELECT * FROM users WHERE AGE > 10 AND AGE < 30 for update;

在上方的例子中,在事务1中,咱们并未在事务刚启动时立刻加锁,而是启动了一次性个别的查问,随后事务2完成拔出数据后,事务1再启动了两次查问。

咱们观察到,事务1后两次查问的结果齐全不同。在没有加锁的状况下,即快照读时,读取的数据与第一次性查问结果相反,从而防止了幻读现象。但第二次查问口头了锁定操作,即读,因此读取到的数据中蕴含了其余事务提交的数据,造成了幻读的出现。

倘若您了解了上述例子以及读的概念,您将很容易看法到,上方的这个案例理想上也会造成幻读的出现:

事务1

事务2

SET session TRANSACTION ISOLATION LEVEL REPEATABLE READ;

BEGIN;

SELECT * FROM users WHERE AGE > 10 AND AGE <30;

BEGIN;

INSERT INTO users(gmt_create, age, name) values(now(), 20, 'Paidaxing999');

COMMIT;

SELECT * FROM users WHERE AGE > 10 AND AGE <30;

UPDATE users set name = "Paidaxing888" where age = 20;

SELECT * FROM users WHERE AGE > 10 AND AGE <30;

这里发生幻读的要素和前面的例子实践上是相反的。即,MVCC只能处置快照读中的幻读疑问,而关于读(例如 SELECT FOR UPDATE、UPDATE、DELETE 等操作)仍会造成幻读的发生。在同一个事务中同时启动快照读和读操作时,将造成幻读的出现。

UPDATE 语句也属于读操作,因此它有或许读取到其余事务提交的结果。

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

要素在于依据快照读的定义,在可重复读级别下,假设在身手务中出现了数据修正,将会降级快照数据,因此最后一次性查问的结果也会相应地出现变动。

如何防止幻读

了解了幻读发生的情境以及无法处置的几种状况后,让咱们总结一下如何处置幻读的疑问。

首先,若欲彻底处置幻读疑问,在 InnoDB 中惟一可选的隔离级别是 Serializable(可串行化)级别。

若宿愿在必定水平上处置或防止幻读,可思索经常使用可重复读(RR)隔离级别,但读提交(RC)和读未提交(RU)级别必需无法行。

在可重复读级别中,尽量经常使用快照读(无锁查问),这样不只可以缩小锁抵触、提高并发度,还能防止幻读疑问的出现。

在高并发场景中若必需加锁,应在事务开局时立刻加锁,这将引入间隙锁,有效地防止幻读。

但是,值得留意的是,间隙锁是引发死锁的关键要素,因此在经常使用时须要审慎看待。

您可能还会对下面的文章感兴趣: