mysql 如何解决幻读,别再误解mysql的幻读了

mysql 如何解决幻读,别再误解mysql的幻读了,MySQL如何解决幻读问题

在高并发数据库系统中,需要保证事务之间的隔离和事务本身的一致性。所以要解决幻影阅读的问题。本文就来介绍一下,有兴趣的可以看看。

目录

前言一、什么是魔读?二、魔读有什么不好?(1)需要单独解决。(2)间隙锁导致的并发。(3)如何解决幻影阅读?三。摘要

前言

我们知道MySQL在可重复隔离级别看不到其他东西提交的内容。在可提交隔离级别,可以提交其他事务。但是,如果我们的业务场景是一个事物中的两个相同的查询,并且我们需要看到的数据是一致的,并且不受其他事物的影响,我们将使用可重复读取隔离级别。在这种情况下,RR级别的普通查询(快照读取)依赖于MVCC来解决“神奇读取”问题。如果是“现读”的情况,那要靠什么来解决“魔读”的问题呢?这是这篇博文需要讨论的。

在讨论之前,可以先看看之前的博文(MySQL是如何实现事务隔离的?),主要介绍隔离级别的具体技术细节。看完这篇文章可能对你更有帮助。

注意:这篇博文中讨论的“神奇阅读”指的是“可重复阅读”隔离级别。

一、什么是幻读?

假设我们有下面的表T结构,其中的初始数据行为:(0,0,0),(1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5)

创建表` t

` id INT(11)不为空,

` key INT(11)默认为空,

` value INT(11)默认为空,

主键(` id `),

键“值”(“值”)

)ENGINE=InnoDB

插入t中

值(0,0,0),

(1, 1, 1),

(2, 2, 2),

(3, 3, 3),

(4, 4, 4),

(5, 5, 5)

假设select * from where value=1进行更新,只有这一行被锁定(注意这只是一个假设),其他行都没有被锁定,那么会出现如下场景:

会话的三个查询Q1-Q3都是select * from where value=1 for update的行,查询的值=1。

T1: Q1只返回一行(1,1,1);

T2:会话B将id=0的值更新为1。此时,表t中有两行值=1的数据。

T3: Q3返回两行(0,0,1),(1,1,1)

T4:会话C插入一行(6,6,1)。此时,表t中有三行值=1的数据。

T5: Q3返回三行(0,0,1),(1,1,1),(6,6,1)

T6:会话A事物提交。

当Q3读到同样的value=1的现象时,称为魔读。幻读是指当一个事务前后两次查询同一个范围时,后一个查询看到了前一个查询没有看到的行。

解释“魔法阅读”如下:

在可重复读取隔离级别下,普通查询是快照读取,不会看到其他事务插入的数据。所以魔读只会出现在“当前阅读”下(三个查询都是为了更新表示当前阅读);

上面会话B的修改更新结果是用“当前读数”在会话A之后的select语句看到的,不能称之为魔读。魔读仅指“新插入的行”。

二、幻读有什么问题?

(1)需要单独解决

众所周知,选择.for update语句的作用是锁定相应的数据行。例如,会话A在T1的Q1查询语句:select * from where value=1 for update锁定值为1的数据行。但显然,如果发生上述场景,此时for update的语义就断了(value=1的数据行没有被锁定)。

即使锁定了所有记录,新插入的记录也无法停止,所以要单独解决“魔读”的问题。这不是MVCC或线锁机制可以解决的。这导致了“间隙锁”,这是另一种锁定机制。

(2)间隙锁引发的并发度

引入间隙锁后,可能会导致同一个语句锁定更大的范围,从而影响并发性。具体请看下面的介绍。

三、如何解决幻读?

魔读的原因是行锁只能锁行,但是插入新记录的动作更新了记录之间的“间隙”。因此,为了解决幻影读取的问题,InnoDB不得不引入一种新的锁,即Gap Lock。

Gap:例如,在表中添加6条记录,0,5,10,15,20,25。产生了七个间隙:

在逐行扫描的过程中,不仅对行添加行锁,还对行两侧的间隙添加间隙锁。这确保了不会插入新记录。

间隙锁和行锁称为下键锁,每个下键锁是一个前开后关区间(间隙锁区间,下键锁前开后关区间):

gap lock和gap lock没有冲突,冲突的是在gap中插入一条记录。

表中没有value=7的数据,所以Q1添加了间隙锁(1,5),Q2也添加了这个间隙锁。两者不冲突保护这个缺口,不允许插入值。

在表T初始化之后,假设表的数据如下:

如果使用select * from for update,则整个表的所有记录都将被锁定,导致7个next-key锁,

分别是(-,0]、(0,2]、(2,4]、(4,6]、(6,8]、(8, 10]、(10, +supremum]

锁的引入可能会导致同一语句锁定更大的范围,但会影响并发性。

假设发生以下情况:

那么显然会出现死锁,分析如下:

Q1:执行select …for update语句,由于行id=9不存在,将添加gap lock (8,10);

Q2:在执行select …for update语句时,还会添加gap锁(8,10),gap锁之间不会有冲突,所以这条语句可以成功执行;

会话B试图插入一行(9,9,9)被会话A的gap锁阻塞,只好等待;

会话A试图插入一行(9,9,9),但是被会话b的间隙锁阻塞了。

随着上面提到的gap lock的引入,同一个语句可能被锁定在更大的范围内,实际上影响了并发性。

为了解决幻影读取的问题,我们可以采用读取可提交隔离级别,间隙锁只有在可重复读取隔离级别下才会生效。因此,如果隔离级别设置为read commit,就不会有间隙锁。同时,要解决可能出现的数据和日志不一致,需要将binlog格式设置为row,也就是说,采用“RC隔离级别日志格式binlog_format=row”的组合。

三、总结

间隙锁只在RR隔离级有效,RC隔离级没有间隙锁。

解决RR隔离级“幻读”问题:“快照读”依赖MVCC控制,“当前读”通过gap lock解决;

缺口锁和排锁称为下键锁,每个下键锁为一个前开后关区间;

锁的引入可能会导致同一条语句锁定更大的范围,影响并发性。

这篇关于MySQL如何解决魔法阅读问题的文章就到这里了。更多相关MySQL魔读内容,请搜索我们之前的文章或者继续浏览下面的相关文章。希望大家以后能多多支持我们!

mysql 如何解决幻读,别再误解mysql的幻读了