资讯

精准传达 • 有效沟通

从品牌网站建设到网络营销策划,从策略到执行的一站式服务

mysql怎么解决幻读的 别再误解mysql的幻读了

mysql可重复读的幻读解决方案

首先需要明确的就是“幻读”概念: 隔离级别是可重复读,在一个事务中前后两次查询,查到了其他事务insert进来的数据。

成都创新互联是专业的安陆网站建设公司,安陆接单;提供成都网站设计、网站建设,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行安陆网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!

强调的是读取到了其他事务插入进来的数据。

下面来论证一下可重复读下幻读的解决方案

先明确一下,for update语法就是当前读,也就是查询当前已经提交的数据,并且是带悲观锁的。没有for update就是快照读,也就是根据readView读取的undolog中的数据。

如果按照以上猜想,那么整个执行结果就违背了 可重复读 的隔离级别了。

那么我们再假设select * from TABLE where d = 5 for update;这条语句锁定的是所有被扫描到的数据。

这是因为T2阶段的update会被阻塞住,毕竟所有被扫描到的记录都被锁定了。

按照上述推理过程,很显然,即使锁定所有扫描到的数据行,也依然存在幻读的情况。违背了 可重复读 的隔离级别。

针对这个情况,我们要解决幻读的问题,那么就要求针对所有被扫描的记录行以及还不存在的d=5的记录行都给锁住。

至此,当前查询结果完全满足 可重复读 的隔离级别。

通过以上推论,我们可以总结一下,在可重复读的隔离级别下,解决幻读除了需要锁定所有扫描到的记录行外,还需要锁定行之间的间隙,也就是通过间隙锁来解决幻读的问题。

正确理解MYSQL的幻读

一、定义

1、幻读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.

翻译一下:

所谓的幻影行问题是指,在同一个事务中,同样的查询语句执行多次,得到了不同的行结果集。

例如,如果同一个SELECT语句执行了两次,第二次执行的时候比第一次执行时多出一行,则该行就是所谓的幻影行。

2、幻读与不可重复读的区别

从官方的定义来看,幻读的定义侧重于多条记录,就是记录条数的变化,而不可重复读侧重于单条记录数据的变化,这样区分原因在于解决幻读需要范围锁,解决不可重复读只需要单条记录加锁

二、InnoDB的REPEATABLE READ级别

InnoDB支持由SQL1992标准描述的所有四个事务隔离级别,默认隔离级别是 REPEATABLE READ。

1、快照读:

在RR模式下,第一次读取会建立快照,后续查询会读取快照。

这意味着,如果在同一事务中发出多个普通[ SELECT ](非锁定)语句,则这些 [ SELECT ]语句的结果也是一致的。

2、[locking reads](锁定读取,又叫当前读)

[ SELECT ]语句中使用 FOR UPDATE 或 FOR SHARE

3、行锁

在RR模式下,使用当前读以及 [ UPDATE ]和 [ DELETE ]语句会对数据记录加行锁,锁定范围取决于该语句使用的是具有唯一搜索条件的唯一索引还是范围类型搜索条件。

三、InnoDB的READ COMMITTED级别

1、在RC模式下,每次读取都会刷新快照,因此不能保证可重复读

2、在RC模式下,使用当前读以及 [ UPDATE ]和 [ DELETE ]语句会对数据记录加行锁,但是不会加范围锁,间隙锁定仅用于外键约束检查和重复键检查。

3、由于禁用了间隙锁定,因此可能会产生幻影行问题,因为其他会话可以在间隙中插入新行。

4、 对于[ UPDATE ]或 [ DELETE ]语句, InnoDB 仅对其更新或删除的行持有锁。MySQL评估 WHERE 条件后,将释放不匹配行的记录锁 。这大大降低了死锁的可能性,但是仍然可以发生。

5、对于[ UPDATE ]语句,如果某行已被锁定,则 InnoDB 执行“半一致”读取,将最新提交版本的数据返回给MySQL,以便MySQL可以确定该行是否符合 WHERE 条件。如果该行匹配(必须更新),则MySQL会再次读取该行,这一次 InnoDB 会将其锁定或等待获取锁。

6、注意

从MySQL 8.0.22开始,DML操作(增删改,通过联接列表或子查询)从MySQL授权表中读取数据,但不对其进行修改,无论隔离级别如何,都不会在MySQL授权表上获得读取锁。

有关更多信息,请参见 Grant Table Concurrency 。

四、乐观锁与悲观锁

1、乐观锁

在UPDATE的WHERE子句中加入版本信息来确定修改是否生效

使用乐观锁时仍然需要非常谨慎,因为RR是可重复读的,在UPDATE之前读取版本号,应该使用[当前读],不能使用[快照读]

2、悲观锁

在UPDATE执行前,SELECT后面加上FOR UPDATE来给记录加锁,保证记录在UPDATE前不被修改。SELECT ... FOR UPDATE是加上了X锁,也可以通过SELECT ... LOCK IN SHARE MODE加上S锁,来防止其他事务对该行的修改。

3、无论是乐观锁还是悲观锁,使用的思想都是一致的,那就是当前读。乐观锁利用当前读判断是否是最新版本,悲观锁利用当前读锁定行。

五、总结

1、RC级别没有范围锁一定会导致不可重复读和幻影行

2、RR级别安全性更高,实现可重复读的方式为快照,如果需要最新数据可以选择[当前读],因此RR级别是首选

3、不论RR还是RC级别,增、删、改的操作都会进行一次[当前读]操作,以此获取最新版本的数据,并检测是否有重复的索引。

4、RR级别下,当前事务如果未发生更新操作(增删改),快照版本会保持不变,多次查询读取的快照是同一个

5、RR级别下,当前事务如果发生更新(增删改),会刷新快照,会导致不可重复读和幻影行

6、RR级别下,使用当前读,会刷新快照,会导致不可重复读和幻影行

7、RR级别下,可以通过提交当前事务并在此之后发出新查询来为查询获取更新的快照。

8、RR级别可以部分解决不可重复读和幻读问题

9、其实问题的关键是你的业务逻辑需要可重复读还是最新数据

mysql 解决可提交读、可重复读、幻读

这张图本人觉得总结得挺好的,在一般的互联网项目中,基本上用的都是Innodb引擎,一般只涉及到的都是行级锁,但是如果sql语句中不带索引进行操作,可能会导致锁表,这是不推荐的,性能非常低,可能会导致全表扫描等,行锁的具体实现算法有以下几种mysql特有的锁:

Record Lock(记录锁):单个行记录的锁,一般是唯一索引或者主键上的加锁

Gap Lock(间隙锁):锁定一个区间,但是不包括自身,开区间的锁,RR级别才会有间隙锁,间隙锁的唯一目的是防止区间数据的插入,所以间隙锁与间隙锁之间是不会相互阻塞的

Next-key Lock(临键锁):与间隙锁的区别是包括自身,是左开右闭区间,RR级别才会有

加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug”。

原则 1:加锁的基本单位是 next-key lock,希望你还记得,next-key lock 是前开后闭区间。

原则 2:查找过程中访问到的对象才会加锁。

优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。

优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。

一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

举例来说明上述的原则:

建表

插入数据:

INSERT INTO t ( id , c , d ) VALUES (0, 0, 0);

INSERT INTO t ( id , c , d ) VALUES (5, 5, 10);

INSERT INTO t ( id , c , d ) VALUES (10, 10, 10);

INSERT INTO t ( id , c , d ) VALUES (15, 15, 15);

INSERT INTO t ( id , c , d ) VALUES (20, 20, 20);

INSERT INTO t ( id , c , d ) VALUES (25, 25, 25);

例子1:锁表

因为d字段上没有建索引,所以涉及该字段的查询加锁会锁住整个表

因为d字段上面没有建立索引,所以事务1执行后会导致整个表被锁,后面所有的操作都会在等待整个表锁被释放

例子2:主键/唯一索引 记录锁

id字段为主键,而且事务1查询命中了唯一的记录,默认是加Next-key Lock,区间是(0,5],但是根据优化1,唯一索引/主键上的等值查询,会退化为行锁,所以只会锁5这个记录。

例子3:主键/唯一索引上的间隙锁

由于表 t 中没有 id=7 的记录,所以用我们上面提到的加锁规则判断一下的话:根据原则 1,加锁单位是 next-key lock,事务1加锁范围就是 (5,10];同时根据优化 2,这是一个等值查询 (id=7),而 id=10 不满足查询条件,next-key lock 退化成间隙锁,因此最终加锁的范围是 (5,10),所以事务2会阻塞,事务3执行成功。

例子4:普通索引上的间隙锁

c字段是普通索引,事务1执行时默认是对区间(0,5]加间隙锁,根据优化2,非唯一索引/主键会继续向右遍历,找到10,所以最终的加锁为(0,5]的Next-Key锁+(5,10)的间隙锁,所以事务2阻塞,事务3成功。

例子5:间隙锁与行锁

事务1默认的Next-Key锁区间是(0,5],根据优化2会向右遍历,找到不满足查询条件的10,退化成间隙锁,所以事务1的锁是(0,5]的Next-Key锁+(5,10)的间隙锁,这两个锁与行锁是冲突的,而事务2申请的Next-Key锁是和事务1一样,但是c=5的行锁与事务1冲突,所以产生了阻塞,如果改为update t set d=1000 where c=6;因为此时产生的间隙锁为(5,10),而间隙锁与间隙锁是不冲突的,不会产生阻塞

例子6:lock in share mode锁覆盖索引

事务1存在覆盖索引的情况,不会去回表,lock in share mode这种情况下只会锁c字段索引,而事务2是对主键加行锁,所以两者不存在冲突。

例子7:主键/唯一索引上的范围查询

开始执行的时候,要找到第一个 id=10 的行,因此本该是 Next-Key Lock(5,10],根据优化 1, 主键 id 上的等值条件,退化成行锁,只加了 id=10 这一行的行锁。范围查找就往后继续找,找到 id=15 这一行停下来,因此需要加 Next-Key Lock(10,15],所以事务3是冲突的。

例子8:普通索引上的范围查询

开始执行时,找到第一个满足条件的行10,加锁Next-Key Lock(5,10],因为不是唯一索引,所以不会退化,继续向后面找,找到15这一行停下来,因此需要加 Next-Key Lock(10,15],因为是范围查询,所以锁不会退化。

快照读: 通过MVCC实现,该技术不仅可以保证innodb的可重复读,而且可以防止幻读,但是他读取的数据虽然是一致的,但是数据是历史数据。

简单的select操作(不包括 select … lock in share mode, select … for update)

当前读: 要做到保证数据是一致的,同时读取的数据是最新的数据,innodb提供了next-key lock,即gap锁与行锁结合来实现。

select … lock in share mode

select … for update

insert

update

delete

自己理解:

简单的select是快照读,快照读实现可提交读,可重复读和幻读是通过MVCC+ReadView实现的,而当前读实现这几种是通过锁来实现的,为了说明具体原理,下面介绍下MVCC和ReadView概念,所以简单的select是通过乐观锁实现的,当前读是通过悲观锁实现的。

参考文章:


当前题目:mysql怎么解决幻读的 别再误解mysql的幻读了
网站路径:http://cdkjz.cn/article/ddehjgi.html
多年建站经验

多一份参考,总有益处

联系快上网,免费获得专属《策划方案》及报价

咨询相关问题或预约面谈,可以通过以下方式与我们联系

业务热线:400-028-6601 / 大客户专线   成都:13518219792   座机:028-86922220