之前我们最开始的几篇文章就讲过,你除了写redolog日志还必须要写undo log日志,这个undo log日志是至关重要的,没有他,你根本都没办法回滚事务!
对于我们的业务系统去访问数据库而言,他往往都是多个线程并发执行多个事务的,对于数据库而言,他会有多个事务同时执行,可能这多个事务还会同时更新和查询同一条数据,所以这里会有一些问题需要数据库来解决,如下图:
每个事务都会执行各种增删改查的语句,把磁盘上的数据页加载到buffer pool的缓存页里来,然后更新
缓存页,记录redo log和undo log,最终提交事务或者是回滚事务,多个事务会并发干上述一系列事情。
多线程并发执行多个事务带来的问题如下。
本质就是事务B去修改了事务A修改过的值,但是此时事务A还没提交,所以事务A随时会回滚,
导致事务B修改的值也没了,这就是脏写
本质其实就是事务B去查询了事务A修改过的数据,但是此时事务A还没提交,所以事务A随时会回滚导致事务B再次查询就读不到刚才事务A修改的数据了!这就是脏读。
无论是脏写还是脏读,都是因为一个事务去更新或者查询了另外一个还没提交的事务更新过的数据。因为另外一个事务还没提交,所以他随时可能会反悔会回滚,那么必然导致你更新的数据就没了,或者你之前查询到的数据就没了,这就是脏写和脏读两种坑爹场景。
假设我们有一个事务A开启了,在这个事务A里会多次对一条数据进行查询
假设是事务A只能在事务B提交之后读取到他修改的数据(避免脏读)
其实要说没问题也可以是没问题,毕竟事务B和事务C都提交之后,事务A多次查询查到他们修改的值,是ok的。
但是你要说有问题,也可以是有问题的,就是事务A可能第一次查询到的是A值,那么他可能希望的是在
事务执行期间,如果多次查询数据,都是同样的一个A值,他希望这个A值是他重复读取的时候一直可以
读到的!他希望这行数据的值是可重复读的!
这个问题简单来说,就是一个事务多次查询一条数据,结果每次读到的值都不一样,这个过程中可能别的事务会修改这条数据的值,而且修改值之后事务都提交了,结果导致人家每次查到的值都不一样,都查到了提交事务修改过的值,这就是所谓的不可重复读。
幻读指的就是你一个事务用一样的SQL多次查询,结果每次查询都会发现查到了一些之前没看到过的数
据注意,幻读特指的是你查询到了之前查询没看到过的数据!此时就说你是幻读了。
这4种级别包括了:read uncommitted(读未提交),read committed(读已提交),repeatable read(可重复读),serializable(串行化)
事务级别 | 可能出现的问题 | 备注 |
---|---|---|
读未提交 | 脏读,不可重复读,幻读 | |
读已提交RC | 不可重复读,幻读 | |
可重复读 RR | 幻读 | |
串行化 | 性能降低 | 根本就不允许你多个事务并发执行 |
但是要注意的一点是,MySQL默认设置的事务隔离级别,是RR级别的(一般数据库是RC)。并且MySQL的RR级别的语义跟SQL标准的RR级别不同的,毕竟SQL标准里规定RR级别是可以发生幻
读的,但是MySQL的RR级别就避免了幻读。
MySQL里执行的事务,默认情况下不会发生脏写、脏读、不可重复读和幻读的问题,事务的执行都是并行的,大家互相不会影响,我不会读到你没提交事务修改的值,即使你修改了值还提交了,我也不会读到的,即使你插入了一行值还提交了,我也不会读到的,总之,事务之间互相都完全不影响!
当然,要做到这么神奇和牛叉的效果,MySQL是下了苦功夫的,后续我们接着就要讲解MySQL里的
MVCC机制,就是多版本并发控制隔离机制,依托这个MVCC机制,就能让RR级别避免不可重复读和幻读的问题。但是一般来说,真的其实不用修改这个级别,就用默认的RR其实就特别好,保证你每个事务跑的时候都没人干扰,何乐而不为呢
每条数据其实都有两个隐藏字段,一个是trx_id,一个是roll_pointer,这个trx_id就是最近一次更新这条数据的事务id,roll_pointer就是指向你了你更新这个事务之前生成的undo log。
多个事务串行执行的时候,每个人修改了一行数据,都会更新隐藏字段txr_id和roll_pointer,同时之前多个数据快照对应的undo log,会通过roll_pinter指针串联起来,形成一个重要的版本链!
生成readview时机
RC隔离级别:每次读取数据前,都生成一个readview;
RR隔离级别:在第一次读取数据前,生成一个readview;
当我们执行一个事务的时候,就给你生成一个ReadView,里面比较关键的东西有4个
readview四个主要元素
m_ids:表示在生成readview时,当前系统中活跃的读写事务id列表;
min_trx_id:表示在生成readview时,当前系统中活跃的读写事务中最小的事务id,也就是m_ids中最小的值;
max_trx_id:表示生成readview时,系统中应该分配给下一个事务的id值;
creator_trx_id:表示生成该readview的事务的事务id;
readview判断版本链
有了readview,在访问某条记录时,按照以下步骤判断记录的某个版本是否可见
通过undo log多版本链条,加上你开启事务时候生产的一个ReadView,然后再有一个查询的时候,根
据ReadView进行判断的机制,你就知道你应该读取哪个版本的数据。而且他可以保证你只能读到你事务开启前,别的提交事务更新的值,还有就是你自己事务更新的值。假如说是你事务开启之前,就有别的事务正在运行,然后你事务开启之后 ,别的事务更新了值,你是绝对读不到的!或者是你事务开启之后,比你晚开启的事务更新了值,你也是读不到的!通过这套机制就可以实现多个事务并发执行时候的数据隔离。
在MySQL中让多个事务并发运行的时候能够互相隔离,避免同时读写一条数据的时候有影响,是依托undo log版本链条和ReadView机制来实现的。
第一步:首先我们还是假设有一条数据是事务id=50的一个事务插入的,同时此时有事务A和事务B同时在运行,事务A的id是60,事务B的id是70。
第二步:这个时候,事务A发起了一个查询,他就是第一次查询就会生成一个ReadView
第三步:事务A基于这个ReadView去查这条数据,通提供过对比这条数据的事务id和ReadView比较
第四步:事务B此时更新了这条数据的值为值B,此时会修改trx_id为70,同时生成一个undo log版本链,而且关键是事务B此时他还提交了,也就是说此时事务B已经结束了,如下图所示。
这个时候大家思考一个问题,ReadView中的m_ids此时还会是60和70吗?那必然是的,因为ReadView一旦生成了就不会改变了,这个时候虽然事务B已经结束了,但是事务A的ReadView里,还是会有60和70两个事务id。
第五步:事务A继续查询数据,结果发现这条数据的trx_id为70,如果被访问版本的trx_id,值在readview的min_trx_id和max_trx_id之间,就需要判断trx_id属性值是不是在m_ids列表中,而我们发现我们的ReadView中,刚好有70。说明创建Readview时生成该版本的事务B还是活跃的,该版本不可以被访问。
因此这个时候只能顺着指针往历史版本链条上,找到下面一条数据,trx_id为50,是小于ReadView的min_trx_id的,说明在他开启查询之前,就已经提交了这个事务了,所以事务A是可以查询到这个值的,此时事务A查到的是原始值。
你事务A多次读同一个数据,每次读到的都是一样的值,除非是他自己修改了值,否则读到的一直会一
样的值。不管别的事务如何修改数据,事务A的ReadView始终是不变的,他基于这个ReadView始终看到的值是一样的!
接着我们来看看幻读的问题他是如何解决的。假设现在事务A先用select * from x where id>10来查
询,此时可能查到的就是10条数据,而且读到的是这条数据的原始值的那个版本。具体原因同上
如果被访问版本的trx_id,值在readview的min_trx_id和max_trx_id之间,就需要判断trx_id属性值是不是在m_ids列表中?
如果在:说明创建readview时生成该版本的事务还是活跃的,该版本不可以被访问
如果不在:说明创建readview时生成该版本的事务已经被提交,该版本可以被访问;
第一步:现在有一个事务C插入了一条数据
第二步:此时事务A再次查询,此时会发现符合条件的有12条数据,10条是原始值那个数据,2条是事务C插入的那条数据,但是事务C插入的那条数据的trx_id是80,这个80是大于自己的ReadView的max_trx_id的,说明是自己发起查询之后,这个事务才启动的,所以此时这条数据是不能查询的。
因此事务A本次查询,还是只能查到原始值10条数据,如下图。
所以大家可以看到,在这里,事务A根本不会发生幻读,他根据条件范围查询的时候,每次读到的数据
都是一样的,不会读到人家插入进去的数据,这都是依托ReadView机制实现的。
multi-version concurrent control,就是多版本并发控制机制。
MySQL实现MVCC机制的时候,是基于undo log多版本链条+ReadView机制来做的,默认的RR隔离级别,就是基于这套机制来实现的,依托这套机制实现了RR级别,除了避免脏写、脏读、不可重复读,还能避免幻读问题。因此一般来说我们都用默认的RR隔离级别就好了。
这就使得别的事务可以修改这条记录,反正每次修改都会在版本链中记录。SELECT可以去版本链中拿记录,这就实现了读-写,写-读的并发执行,提升了系统的性能。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://lebron.blog.csdn.net/article/details/124831482
内容来源于网络,如有侵权,请联系作者删除!