在高并发数据库系统中,需要保证事务之间的隔离和事务本身的一致性。所以要解决幻影阅读的问题。本文就来介绍一下,有兴趣的可以看看。
目录
前言一、什么是魔读?二、魔读有什么不好?(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魔读内容,请搜索我们之前的文章或者继续浏览下面的相关文章。希望大家以后能多多支持我们!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。