MYSQL实战优化——事务、undo log版本链

MYSQL实战优化——事务、undo log版本链,第1张

今天我们来看看多个事务对缓存页里的同一条数据同时进行更新或者查询,此时会产生哪些问题?这里实际会涉及到 脏写、脏读、不可重复读、幻读, 四中问题。

这个脏写的话,它的意思是说有两个事务,事务A和事务B同时在更新一条数据,事务A先把它更新为A值,事务B紧接着就把它更新为B值。事务A是先更新的,它在更新之前,这行数据的值为NULL,所以此时事务A的undo log日志大概是这样的:更新之前这行数据的值为NULL,主键为XX 。

那么此时事务B更新完了数据的值为B,结果此时事务A突然回滚了,那么就会用它的undo log日志去回滚。此时事务A一回滚,直接就会把那行数据的值更新回之前的NULL值。所以对于事务B看到的场景,就是自己明明更新了,结果值却没了,这就是 脏写。

假设事务A更新了一行数据的值为A,此时事务B去查询了一些这行数据的值,看到的值是A,然后事务B拿着刚查询到的A值去处理各种业务。但是此时不幸的事情发生了,事务A突然回滚了,导致它刚才更新的A值没了,此时那行数据的值回滚为NULL值。这就是所谓的 脏读。 它的本质是事务B去查询了事务A修改过的数据,但是此时事务A还没有提交,事务A随时会回滚导致事务B查询了一个不存在的值。

接着我们来看一下 的问题,假设我们有一个事务A开启了,在这个事务A里会多次对一条数据进行查询。然后另外有两个事务,一个是事务B,一个是事务C,它们都是对一条数据进行更新的。假设缓存页里一条数据原来的值是A值,此时事务A开启之后,第一次查询这条数据,读取到的是A值。接着事务B更新了那行数据的值为B,同时提交事务,然后事务A第二次查询该行数据,此时查到的是事务B修改过的值B 。接着事务C更新了那行数据的值为C,同时提交事务,然后事务A第三次查询该行数据,此时查到的是事务C修改过的值C值。

那么上面的场景有什么问题呢?其实要说没问题也是可以的,毕竟事务B和C都提交事务了。但是要说有问题也是可以的,就是事务A可能第一次查询到的是A值,那么它可能希望的是在事务执行期间,如果多次查询数据,都是同样的一个A值。但是该场景下,A值明显不是可重复读的。

这种情况算不算一个问题呢?其实这是根据你的业务决定的。有的业务要的是可重复读,而有的业务却需要不可重复读。

假设一个事务A先发送一条SQL语句,里面有一个条件,要查询一批数据出来,比如“select * from table where id >10”,类似这种SQL,它一开始查询出了10条数据。然后事务B往表里插入了几条数据,而且事务B还提交了。此时事务A再次查询,由于事务B插入了几条数据,导致这次它查询出来了12条数据。同样的SQL语句,两次的查询结果却不一样,所以开始怀疑自己是不是出现了幻觉?导致刚才幻读了?这就是幻读一词的由来。

在SQL标准中规定了4种事务隔离级别,就是说多个事务并发运行的时候,互相是如何隔离的,从而避免一些事务并发问题。这4种级别包括了: read uncommitted(读未提交)、read committed(读已提交)、repeatable read(可重复读)、serializable(串行化)

第一个read uncommitted隔离级别是不允许发生脏写的。也就是说,不可能两个事务在没提交的情况下去更新同一行数据的值,但是在这种隔离级别下,可能发生脏读、不可重复读、幻读。所以一般来说,是没有人做系统开发的时候把事务隔离级别设置为读未提交这个级别的。

第二个是read committed隔离级别,也就是俗称的RC级别,这个级别不会发生脏写和脏读。也就是说,别的事务没提交的情况下修改的值,你是绝对读不到的。但是,可能会发生不可重复读和幻读问题。

第三个是repeatable read隔离级别,也就是俗称的RR级别,就是可重复读级别。这个级别下,不会发生脏写、脏读、不可重复读的问题。事务一旦开启,多次查询一个值,会一直读到同一个值。但是它会发生幻读的问题。

最后一个隔离级别,就是serializable级别,这种级别,根本不允许多个事务并发执行,只能串行执行,所以不可能有幻读问题。但是这种级别一般除非脑子坏了,否则不可能设置这种级别。

MySQL默认设置的事务隔离级别都是RR级别的,而且MySQL的RR级别是可以避免幻读发生的。

下面的命令可以修改MySQL的默认事务隔离级别:

另外,给大家一个彩蛋,假设你在开发业务系统的时候,比如用spring里的@Transaction注解来做事务这块,假设某个事务你就是有点手痒,想搞成RC级别,那么没问题,在@Transaction注解里是有一个isolation参数的,里面是可以设置事务隔离级别的,具体的设置方式如下:

@Transaction(isolation=Isolation.DEFAULT),默认的就是DEFAULT值,这个就是MySQL默认支持什么隔离就是什么隔离级别。但是你可以手动改成其它的隔离级别,比如,isolation = Isolation.READ_COMMITTED级别,此时你就可以读取到其它事务已提交的数据。

简单来说,我们每条数据其实都有两个隐藏字段,一个是trx_id,一个是roll_pointer,这个trx_id就是最近一次更新这条数据的事务id,roll_pointer就是指向了你更新这个事务之前生成的undo log,关于undo log之前都讲过了。

举个例子,假设有一个事务A(id=50),插入了一条数据,那么此时这条数据的隐藏字段以及指向的undo log如下图所示:

插入的这条数据的值是A,因为事务A的id是50,所以这条数据的trx_id就是50,roll_pointer指向一个空的undo log,因为之前这条数据是没有的。接着有一个事务B修改了一下这条数据,把值改成了B,事务B的id是58,那么此时更新之前会生成一个undo log记录之前的值,然后会让roll_pointer指向这个实际的undo log回滚日志,如下图所示:

当多个事务并发执行的时候,会导致什么问题?

我们知道,执行sql是在buffer pool中对数据进行查询或者修改。如若多个事务同时更新一行数据会出现什么问题?

当事务A和事务B同时去更新同一行数据时,事务A先更新,事务B后更新。

那么此时,undo log就会记录了事务A所改数据的旧值,假设旧值为 null。随后事务B也对该行数据进行了更新,覆盖掉A更新的值。此时事务A突然发生回滚,那么就会根据它的undo log进行回滚。

事务A进行了回滚,那么该数据的值就变成了更新前的null值。

然而,事务B并不知道此事,发现自己更新的值没有了。这就是 脏写 。

本质上,就是一个事务修改了另外一个没提交的事务的值(没提交有可能回滚),而导致有可能数据前后不一致的问题。

同样有事务A和事务B。事务A去更新了一行数据,事务B刚好查询到了该行数据,此时事务B拿到的值为A更新的值。

事务B拿到值后便去业务系统进行各种业务逻辑处理等等,这时,事务A突然回滚了,又把undo log的值回滚到该行数据。紧接着事务B再次查询该行数据的时候,发现前后的值不一样。这就是 脏读 。

本质上,就是一个事务查询到了另个一个未提交的事务的值,而导致有可能数据前后不一致的问题。

在避免脏读的前提下,还有可能出现的 不可重复读 。

这类情况是在什么场景下发生的呢?

假设,有一个前提,事务B在更新某行数据,但暂未提交,在未提交事务的时间里,事务A是读不到该行数据的。必须等事务B提交了,事务A才能读取到它修改的值。这样就可以避免脏读。

这时,假设事务A第一次查询到的值为A值。

事务B把该行数据的值改为B值并立即提交事务。而事务A尚未提交事务,在事务执行期间进行第二次查询,所以事务A第二次查询到的值为B值。

紧接着事务C再次更新数据为C值,并提交了事务。此时,事务A在未提交事务的情况下,进行第三次查询,查到的值为C。

不可重复读就是以上这种情况,事务A未提交事务,每次读到的数据可能都不一样。

通过以上分析,那可重复读,就很好理解了。即希望,事务A每次读到的值都是A值。

假设事务A需要多次批量查询数据,第一次查询到了十条数据

此时事务B往表里插入了几条数据,且B提交了事务,那么此时,就会多出几行数据

接着事务A再次进行查询时,由于事务B的提交,导致事务A查询多出来了几条数据

这样就出现了和查询第一次没见到的数据,就是 幻读 。

本质上,就是一个事务用一样的sql进行多次查询,每次查询到没见过的数据。


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/zaji/7636801.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-08
下一篇2023-04-08

发表评论

登录后才能评论

评论列表(0条)

    保存