mysql幻读如何解决_图解脏写、脏读、不可重复读、幻读

6fff0c1ff3f41843c424075bc3b4297b.png

MySQL 是支持多事务并发执行的,否则来一个请求处理一个请求,处理一个人请求的时候,别的人都等着,这网站就别做了,用户都要砸键盘了。

这里就有一个问题了:一个事务在写数据的时候,另一个事务要读这行数据,该怎么处理?一个事务在写数据,另一个数据也要写这行数据,又该怎么处理这个冲突?其实吧,为了解决这些问题,MySQL 可以说是煞费苦心,使用了 MVCC 多版本控制机制、事务隔离机制、锁机制等。

你一定听过事务可以分成 4 个隔离级别:读未提交、读已提交、可重复读、串行化。上一篇文章「从一个 update 语句开始,来看看 InnoDB 的底层架构原理」中提到的 InnoDB 存储引擎,默认的隔离级别就是是可重复读 REPEATABLE READ。

到这里,不知道你有没有产生几个疑问:

  • 我们知道有事务隔离这回事儿,那为什么要隔离?为什么隔离还不够,还要分多个级别?
  • 事务隔离级别解决了什么问题?没有解决什么问题?
  • MySQL 是如何实现这几个隔离级别的呢?它们底层的工作原理是什么呢?

多个事务并发执行,是怎么一个场景呢?

不知道你第一次听到「事务隔离机制」的时候有什么想法,我的第一反应就是:好好的事务,为什么要给它隔离呢??这么问主要是,对事物并发执行是怎么一个情况,还没有清晰的认识。

多个事务并发执行的场景其实是这样的。我们有一个业务系统,里面很多线程在执行业务代码,比如说 Service 去调用 Dao,Dao 去操作数据库。在业务代码层面,对数据库的操作我们可能会给它加上事务,加上事务之后,如果执行成功就commit,执行失败就要rollback

begin;  # 开始事务insert into runoob_transaction_test value(5);insert into runoob_transaction_test value(6);commit; # 提交事务或者rollback; # 回滚事务

上面这个流程大家肯定很熟了吧。接下来,MySQL 会把磁盘中的数据加载到 Buffer Pool,再对 Buffer Pool 中数据执行增删改查操作。同时会写入 undo log 和 redo log 等一系列操作。

对上面这个流程不清楚的不清楚的,可以看「从一个 update 语句开始,来看看 InnoDB 的底层架构原理」这篇文章。

上面讲的是一个事务执行的大致流程。现在每个线程都执行一个事务,那就同时有多个线程去调 MySQL 的接口,来操作 Buffer Pool 中的数据。

第二个问题就来了:多个事务并发又咋了,有什么问题么?是有问题,因为允许多个事务并发执行,那它们就可能同时访问同一行数据。

fc8220f6dfd7933826f6be610f895955.png
  • 写冲突,多个事务同时对缓存页里面的一行数据进行更新,允许谁来写?这个冲突要怎么解决?可不可以用锁来解决?
  • 读写冲突,一个事务在写数据,别的事务过来读数据了,这个时候要怎么办?

事务的隔离机制就是解决读写冲突的一个手段。


现在你已经知道了多个事务并发执行是怎么样的一个场景了,也知道会产生写冲突或者读写冲突,事务的隔离机制主要就是用来解决读写冲突的问题。

具体来说就是:脏写、脏读、不可重复读和幻读

脏写

MySQL 的数据是放在一个个缓存页里面的,然后每个缓存页里面是一行行的数据,就像下面这张图这样:

956ff8e5902ba6c0c5b9612daafed394.png

现在有一个事务 A 正在执行,它执行的是一个写操作,原来有一行数据是 NULL,在它执行了 update 操作,把 NULL 改成了值 A,就像下面这张图这样:

0c1048f084536343774f7eed91691112.png

注意,这里事务 A 更新了一行数据但是它并没有提交。紧接着事务 B 也来写这行数据了,这就是多个事务并发执行,还操作同一行数据的场景了。事务 B 做的也是 update 操作,把值 A 改成了值 B,如下图所示:

fc8220f6dfd7933826f6be610f895955.png

前面说了事务 A 此时是没有提交的,除了提交事务,还可以干嘛?对了,事务 A 是可以回滚的!回滚意味着什么?回滚是不是就意味着原来那一行数据,要回滚到事务 A 执行之前的那个值,也就是 NULL:

73e29f193a610204a759a6e873dadd32.png

事务 A 回滚了,对于事务 B 意味着什么?事务 B 明明正常写了一行数据,但是写完之后发现值变了,变成一个莫名其妙的值。

这就是脏写,脏写就是说我两个事务来写同一行数据,但是前面的那个事务反悔了,回滚了。在后面的事务 B 眼里,我明明修改了数据,怎么会写错呢?它打算也想不到,是别的事务回滚了。

脏读

脏读的情况和脏写差不多喽:

1c18f524c9d6bed0acc4c8ec2ec3fd33.png
  1. 事务 A 先写数据,把一行数据的值从 null 改成了 A,同样事务 A 并没有提交;
  2. 然后事务 B 过来读了,它读到的值自然是 A 喽;
  3. 接着事务 A 又回滚了!回滚之后值就要从 A 变回到 NULL;
  4. 事务 B 再去读的时候读到的就是 NULL 了

脏读就是事务 B 因为事务 A 回滚,读不到之前的值了。

不可重复读

47592bc8ecf88affdbf713ae79b28538.png
  1. 事务 A 先去读一行数据,读到值是 A;
  2. 事务 B 去修改数据,改成了 B。这里和前面不一样的地方就在,事务 B 它还提交了,不回滚了。
  3. 事务 A 第二次去读,读到的是 B,和第一次读到的 A 不一样。

那不可重复读是指什么?它是指在同一个事务里面查询同一行数据,每次查到的数据都不一样。是不是和脏读很像,区别在于脏读是由于别的事务回滚导致,而不可重复读读到的其实是已经提交的数据。

幻读

9011aab0ac454f61fa167541b9041fc2.png

最后就剩下幻读了,前面的脏写、脏读、不可重复读,都是针对一行数据来说的,幻读不一样,幻读是指查到了之前没有的一批数据:

  1. 事务 A 里有一个条件查询的语句 select name from t where id > 10,它进行了一次范围查询,查到了 10 行数据;
  2. 然后事务 B 网里面加入了一批数据
  3. 事务 A 再查的用条件查询语句查询的时候,发现查到了 15 条,其中 5 条是之前没见过的。这个事务 A 以为自己出现幻觉了,怎么会多出这么些个数据?这就是幻读了。

思考题:事务隔离如何解决这些问题?

文章的开头提出了这样几个问题:

  • 我们知道有事务隔离这回事儿,那为什么要隔离?为什么隔离还不够,还要分多个级别?
  • 事务隔离级别解决了什么问题?没有解决什么问题?
  • MySQL 是如何实现这几个隔离级别的呢?它们底层的工作原理是什么呢?

这里先回答,使用事务隔离机制是为了避免多事务并发执行时的读写冲突,具体来说就是脏写、脏读、不可重复读、幻读。

那么读未提交、读已提交、可重复读、串行化,这四个隔离级别是如何解决这些问题的呢?两个事务同时写一行数据,这样的写冲突,事务隔离机制能解决么?

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值