转载 

图解脏写、脏读、不可重复读、幻读

分类:    287人阅读    IT小君  2023-08-19 14:02

图片


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 中的数据。

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

图片

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

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



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

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

脏写


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

图片

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

图片

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

图片

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

图片

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

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

脏读


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

图片

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

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

不可重复读


图片

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

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

幻读


图片

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

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

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


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

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

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

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

转载于:http://mp.weixin.qq.com/s?__biz=MzI5MzQ2MDg4Nw==&mid=2247483812&idx=1&sn=dc24a2c69481b27b08da6199be654e07&chksm=ec708f5cdb07064a1f00f12a9fbfa5e278b0721d7d766c0c189d9e05250a1255557313a63a4b#rd

支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者

 工具推荐 更多»