1:多个事务并发更新以及查询数据,为什么会有脏写和脏读问题
- 脏写:假设现在有二个事务A,B,同时对一行数据的某个字段进行修改,初始值是NUll,A事务先将值修改为A_value,那么此时必定会记录一条undo log日志用于回滚,此时B事务又将该值修改成B_value,并且提交了事务,这时候,事务A突然要回滚了,因为A事务的undo log记录的值是NULL,所以此时A事务回滚就会将该值修改成NULL,这时候B事务就懵逼了,我明明将值该成B_value了呀,怎么就变成了NULL呢?所以对于B事务来说明明自己修改了值,但是却为NULL,这就是脏写
- 脏读:还是基于A,B二个事务,如果B事务在提交事务之后需要使用到这个值,本来这个字段的值应该是B_value,但是因为事务A回滚了,导致这个值为NULL了,这时候B事务就惨了,明明是B_value这个值,但是却为NULL,这就是脏读,也就是读取都了其它事务还没有提交的数据
2:什么是不可重复读
- 假设现在有一个事务A需要多次读取id=1的这行数据的字段为name的值,注意,在这多次查询是在同一个事务中,假设第一次读取的时候name=a,这时候,有一个事务B要来更新这个值,假设将a修改成了b,并且提交了事务,这时候事务再次来查询这条记录,却发现name=b了,在同一事务中多次查询同一个记录,发现得到的结果却是不一致的,这就是不可重复读
3:恐怖的幻读
- 假设现在有一个事务A,它需要根据条件查询,比如 select * from user where id > 10;这时候有一个事务B,事务B往这张user表中又插入了几条数据,如果此时事务A需要多次查询 select * from user where id > 10,那么就会发现第二次查询的数量会比第一次查询的数量不一致,也就是在同一个事务中多次查询,结果的数量不一致,这就是幻读
4:SQL标准的4种事务隔离级别
- 我们常常说的最多的就是MySql的事务隔离级别,但是现在要讲的是SQL标准的事务隔离级别,MySql的隔离级别与这个大同小异
- Read Uncommitted(读未提交): 这种是不允许脏写的,但是允许脏读,不可重复读,幻读的存在
- Read committed(读已提交):这种不允许脏写,脏读的存在,但是允许不可重复读,幻读的存在
- REPEATABLE READ(可重复读):这种不允许脏写,脏读,不可重复读,但是还是会发生幻读
- serializable(串行化):脏写,脏读,不可重复读,幻读都不会发生,但是性能最差
5:MySql是如何支持4种事务隔离级别的
- 现在我们知道SQL定义的事务隔离级别中的REPEATABLE READ(可重复读) 是会发生幻读,但是MySql中的REPEATABLE READ(可重复读)(简称RR) 是不会发生幻读的,这是MySql的一个不同点,其它三种的事务隔离级别与SQL定义的事务隔离级别是一样的
6:理解MVCC前奏,undo log链是什么
- MVCC机制:MySql的RR隔离级别是比不会发生脏读,不可重复读,幻读的,也就是事务与事务之间是相互隔离的,底层的实现原理就是MVCC(多版本并发控制机制),但是在了解MVCC之前需要知道undo log链
- undo log链:其实MySql中的每条数据都有二个隐藏字段,一个是trx_id:就是最近更新这条记录的事务id,另外一个是roll_pointer:这个就是指向更新之前的事务的undo log,举个例子 假设现在有一个事务A(id=50),插入了一条记录,那么此时这条数据的隐藏字段以及指向undo log如图所示
这时候有一个事务B(id=58)跑来修改这条数据,将值A改成B,在更新之前会生成一个undo log记录之前的值,然后让roll_pointer指向这个undo log
如果这时候又有一个事务C(id=69)来修改这条记录,也是一样的,如图
所以多个事务在串行执行时,每个事务修改一行数据,都会更新隐藏的trx_id和roll_pointer,同时之前多个undo log会通过roll_pointer指针串联起来,形成undo log链
7:基于undo log多版本链条的ReaView机制到底是什么
- 简单来说,就是你执行一个事务的时候,就给你生成一个ReadView,里面比较关键的的东西有4个
- m_ids:这个就是说此时有哪些事务在MySql里执行还没提交的
- min_trx_id:就是m_ids里最小的值
- max_trx_id:就是说mysql下一个要生成的事务id,就是最大事务id
- creator_trx_id:就是你这个事务的id
8:Read Committed隔离级别是如何基于ReadView机制来实现的
- Read Committed的隔离级别是能够读取到别的事务已经提交的数据,但是还是会发生不可重复读和幻读,看下Read Committed是如何基于ReadView机制来实现的
- 假设我们现在有一行数据,事务id是50,这个id等于50的事务是之前已经提交了的,然后现在有二个活跃的事务,事务A和事务B,如下图
现在的情况是shiwuB要来修改这条数据,所以此时这条数据的事务ID就变成了70,并声称一条undo log,如下图
,那么此时事务A要来读取这条数据,所以事务A会生成一个ReadView,此时ReadView中的min_trx_id=60,max_trx_id=71,creator_trx_id=60
此时读到的数据是trx_id=70,也就是事务B修改后的数据,事务A会去自己的ReadView中找,发现m_ids中有60,70二个事务id,会发现此时事务id=70的事务还没有提交,所以会顺着undo log链往下找,这时候会找到trx_id=50这条记录,会发现50比当前ReadView中最小事务id还小,这就说明这个事务是在之前就已经提交了的,所以事务A会把trx_id=50的这条数据读取出来,没有提交的事务B修改后的数据是不会读取出来的
- 如果此时事务B提交了,那么事务A再次发起查询那么应该是能读取到事务B修改后的数据的,但是此时事务A中的ReadView中还保留着事务B,所以事务A下次再发起查询请求的时候就重新生成一个ReadView,这样ReadView中的m_ids就不会存在事务B了,也就能够读取到事务B修改后并且提交了的数据了
9:MySql最牛的隔离级别是如何基于ReadView来实现的
- 假设已经有一条数据了,trx_id=50,如下图
这时候有二个活跃事务A和事务B,这时候事务A要查询这个数据,那么此时事务A会生成一个ReadView,ReadView中的m_ids中会有60,70二个事务,也就是事务A和事务B,此时事务A读取这行数据,发现trx_id=50,小于自己ReadView中的min_trx_id的,说明这行数据之前已经有事务修改过了的,所以读取出来,这时候事务B修改了这行数据,所以此时这行数据的trx_id=70,如下图
,并且此时事务B还提交了,此时事务A再次发起查询,这时候就不会再次生成ReadView,还是之前的那个ReadView,这时候发现trx_id=70这个值已经存在自己的m_ids了,说明这条数据是在自己事务执行过程中提交的,所以不会读取这行数据,接着往undo log链去找,发现trx_id=50,50小于自己ReadView中的min_trx_id,说明这行数据是在自己事务开始之前就提交了的,所以将这行数据读取出来,这就是不可重复读基于ReadView机制来实现的
- 如何解决幻读呢??
- 事务A发起查询会生成一个ReadView,假设这个ReadView中的m_ids=[60],只有自己一个事务,此时事务查询select * from user where id > 1;这时候查询来是1条数据,这时候事务c插入了几条数据,此时事务c插入的数据的trx_id=80,
这时候,事务A再次发起查询,会发现此时多了一条记录出来,然后多出来的这条记录的trx_id=80,大于自己ReadView中的max_trx_id,也就是说这条数据是在自己开启事务之后执行的,所以事务A不会查询这条数据,那么事务A再次发起查询结果还是一条数据