undo_log版本链

  1. mysql中多个事务并发执行的时候互相隔离,默认是RR级别(可重复读),脏写,不可重复读,幻读都不会发生。
  2. undo_log版本链指的是每条数据都有两个隐藏字段, 一个是trx_id,一个是roll_pointer. trx_id是最近更新的这个数据的事务id,roll_pointer就是指向你更新这个事务之前生成的undo_log。
  3. 假如事务A(id =50)插入一条数据,此时 txr_id=50,undo_log日志为null,事务B(id = 58)修改这条数据,值改为B,此时txr_id=58,roll_pointer指向这个实际操作的undo_log回滚日志(实际是事务A的信息)。事务C(id =69)又来修改值为C,此时trx_id = 69,roll_pointer指向事务B的信息。

    image.png

  4. 多个事务来修改同一条数据,会更新隐藏字段txr_id和roll_pointer操作回滚记录会生成undo_log版本链。

    基于undo_log多版本链条实现的ReadView机制

  5. ReadView指的是在执行事务的时候,生成一个ReadView,里面包括①m_ids:此时有哪些事务在mysql里执行还没提交的;② min_trx_id:m_ids里的最小的值 ③ max_trx_id:mysql下一个要生成的事务id,最大事务id ④ creator_trx_id:这个事务的id

  6. 假设数据库原有一行数据,是原有的事务插入的,事务id=32。此时两个事务并发过来执行,一个是事务A(id=45)查询,一个是事务B(id = 59)修改。事务A直接开启一个ReadView,这个ReadView里的m_ids包含了事务A和事务B[45,59],min_trx_id =45,max_trx_id = 60,create_trx_id=45.事务A查询的时候会判断一下当前这行数据的txr_id是否小于ReadView的min_trx_id,如果小于,说明在你这个事务开启之前,原来的事务已经提交了,可能顺利查询到这行数据。然后事务B修改数据值为B,这行数据的trx_id=59,roll_pointer指向修改之前生成的undo_log,事务 B提交。事务A再来查询,发现这行数据的trx_id大于ReadView里的min_trx_id(45),同时小于max_trx_id(60),说明事务可能是差不多同时开启的。再看trx_id =59是否再ReadView的m_ids列表中,说明修改的事务跟自己是并发执行的,对这行数据不可查询。
  7. 如果查不到修改的数据值,会顺着这条数据的roll_pointer顺着undo_log日志链条往下找,会找到trx_id=32,小于ReadView里的min_trx_id(45).说明这个数据是在事务A提交前修改的,可以查。

image.png

Read Committed隔离级别是如何基于ReadView机制实现的

  1. RC隔离级别是指事务运行期间,只要别的事务修改的数据提交了,就可以读到这个数据。会发生不可重复读和幻读的问题。
  2. RC隔离级别时,每次发起查询都会重新生成一个ReadView。
  3. 假设数据库有一条数据,是事务id= 50的一个事务之前插进去的。现在有两个事务A(id=60)和事务B(id=70).B发起update操作将这条数据值改为了B,所以此时这条数据的trx_id会变成事务B的id=70,同时生成一条undo_log,由roll_pointer指向。这个时候事务A发起查询,生成一个ReadView,此时min_trx_id=60,max_trx_id = 71,create_trx_id = 60.发现当前数据的trx_id=70.属于ReadView的事务id范围之间,说了在生成ReadView之前就有这个活跃的事务,但这个事务B还没提交。所以ReadView的m_ids的任务列表里有[60,70]两个id。基于ReadView机制,事务A无法查询到事务B修改的值,顺着undo_log版本链条往下找,找到trx_id=50的,小于min_trx_id,说明在之前修改的,可以读到。接着事务B提交了,根据RC的隔离级别,事务提交之后,别的事务可以读到的。A再次查询,再次生成一个ReadView,此时min_trx_id=60,max_trx_id=71,create_trx_id =60,m_ids=[60],查询发现这条数据trx_id=70,虽说在max_trx_id范围之间,但是并不在m_ids列表,说明事务B在本次生成ReadView之前就提交了。可以读到B值

    1. 4. RC隔离级别就是每次查询都生成新的ReadView。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22489676/1645612962542-6d047471-c735-4760-acdb-fe8926737e9c.png#clientId=u05433273-8f7c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=251&id=ud9ac187b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=502&originWidth=1327&originalType=binary&ratio=1&rotation=0&showTitle=false&size=240269&status=done&style=none&taskId=u7d3cfc4a-2ca9-4512-921f-501fd35252e&title=&width=663.5)

    RR隔离级别,如果基于ReadView实现的

  4. RR隔离级别,同时可以避免不可重复读,幻读。一个事务读一条数据,无论读多少次都是一个值,别的事务修改数据后,也是读不到的。

  5. RR隔离级别中ReadView一旦生成不会改变了。
  6. 假设数据库有一条数据,是事务id= 50的一个事务之前插进去的。现在有两个事务A(id=60)和事务B(id=70).A发起查询,第一次查询会生成一个ReadView,ReadView中的creator_id=60,min_trx_id=60,max_trx_id =71,m_ids=[60,70],查询时发现这条数据的trx_id=50,小于min_trx_id。直接可读。B更新值为B,trx_id=70,同时生成undo_log,事务B提交。此时ReadView中的m_id=[60,70]不会改变。这说明事务A在查询的时候永远查不到B值,只能顺着undo_log链查找。
  7. 幻读的问题,假如事务A查询select * from x where id >1得到一条数据,读到的数据时这条数据的原始值。事务C(id =80)插入一条,trx_id = 80.此时A再查询出两天数据,刚插入的那条的trx_id=80,大于ReadView的_id,所以这条数据不能查询。