MYSQL专题(十一):MVCC

MVCC前奏:undo log版本链

  • 我们每条数据都有2个隐藏字段,一个是trx_id,一个是roll_pointe

    • trx_id:最近一次更新这条数据的事务id
    • roll_pointer:指向你更新这个事务之前生成的undo log
  • 如果先新增一条数据,roll_pointer指向一个空的undo log,因为之前这条数据是没有的

  • MYSQL专题(十一):MVCC

  • 接着其他事务对这条数据进行操作,会形成一条链
    MYSQL专题(十一):MVCC

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

  • 当我们执行一个事务的时候,就会生成一个ReadView,里面比较关键的东西有4个
    • m_ids:此时有哪些事务在mysql里执行还没提交的
    • min_trx_id:m_ids的最小值
    • max_trx_id:mysql下一个要生成的事务id
    • creator_trx_id :就说你这个事务的id
例子
  • 一开始,数据库有一条数据
    MYSQL专题(十一):MVCC
  • 此时事务A,B要对他进行操作;事务A直接开启一个ReadView,这个ReadView里的m_ids就包含了事务A和事务B的两个id,45和59,然后A的min_trx_id = 45, max_trx_id = 60,creator_trx_id = 45;此时A第一次查询这条数据,会走一个判断,就说判断一下当前这行数据的trx_id是否小于ReadView中的min_trx_id,此时法相trx_id = 32,小于ReadView中的min_trx_id,说明你事务开启之前,修改这行数据的事务早就提交了,此时可以查到这行数据
    MYSQL专题(十一):MVCC
  • 接着B开始修改值
    MYSQL专题(十一):MVCC
  • 此时A再次查询,发现数据行的txr_id = 59,大于min_txr_id, 同时小于max_trx_id 那么说明更新这条数据的事务可能是和自己一起开启的,那么就去看m_ids,发现在里面,说明修改数据的事务和自己在同一时段并发执行然后提交的,所以这行数据不能查询;然后就顺着这条数据的roll_pointer顺着undo log日志链条往下找,就会找到最近的一条undo log,trx_id = 32,此时发现trx_id=32小于ReadView里的min_trx_id,说明这个undo log版本必然是在事务A开启之前就执行且提交的。
  • 那么就查询那个undo log里面的值,这就是undo log多版本链条的作用,他可以保存一个快照链条,让你可以读到之前的快照值,通过ReadView+undo log日志链条机制,就可以保证A不会读到并发B更新的值,只会读到之前最早的值
  • 接着,A修改这行数据
    MYSQL专题(十一):MVCC
  • 如果事务A查询这个,发现trx_id和自己的creator_trx_id一样,说明数据是自己修改的,可以读到自己修改的值。
  • 接着事务C修改值
    MYSQL专题(十一):MVCC
  • 此时A再去查,发现trx_id = 78.大于自己ReadView中的max_trx_id ,说明这个是在A事务开启之后,有另一个事务更新了数据,自己不能看到。此时就会顺着undo log链条往下找,
总结
  • 通过undo log多版本链条,加上开启事务时候生产的一个ReadView,然后再由一个查询的时候,更具ReadView进行判断的机制,就知道应该读取哪个版本的数据,可以保证你只能读到你事务开启之前,别的提交事务更新的值,还有就说你自己事务更新的值。
  • 假如说是你的事务开启之前,就有别的事务再运行,然后你事务开启之后,别的事务更新了值,你绝对读不到。或者是你事务开启之后,比你晚开启的事务更新了值,你也读不到。

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

  • RC:事务运行期间,只要别的事务修改数据还提交了,你就是可以读到别人修改的数据的,所以是会发生不可重复读,幻读的问题
  • 一个非常核心的点在于,当你一个事务设置他处于RC隔离级别的时候,他是每次发起查询,都重新生产一个ReadView。
例子
  • 一开始
    MYSQL专题(十一):MVCC
  • B发起一个更新请求,修改数据,修改完之后,A发起一次插叙你请求,就会生成一个ReadView
    MYSQL专题(十一):MVCC
  • A发现trx_id = 70,属于ReadView的事务id范围之间,说明是他生产ReadView之前就有这个活跃的事务,是这个事务修改了数据的值,但是此时B事务还没提交,所以ReadView中的m_ids活跃事务列表里,就有[60,70]两个id的,所以此时根据ReadView的机制,此时事务A是无法查到事务B修改的值B的。
  • 接着B提交事务,然后A再次查询,会再次生成一个ReadView,此时数据库内活跃的事务只有事务A了,因此min_trx_id = 60, max_trx_id = 71,m_id=[60],此时事务A再次基于这个ReadView去查询,会发现这条数据的t的trx_id不在m_ids里面,就说明再ReadView之前,事务B提交了,所以可以查询到B修改的值B
总结
  • 关键点在于每次查询都会生产新的ReadView。
RR隔离级别如何基于ReadView机制实现
  • RR:可重复读和避免了幻读
  • 关键点在于只有在第一次查询的时候生产了ReadView,后面的ReadView都不变
避免不可重复读
  • 事务A第一次查询的时候,生成了一个ReadView, 此时m_ids = [60,70];然后B更改了数据,并且提交了,事务A再次查询,ReadView是不变的,所以还是不能看到B更改的数据。所以笔迷拿了不可重复读问题;事务A多次读同一个数据,每次独到的都是一样的值,除非他自己修改了值,否则独到的一直会是一样的值。
避免幻读
  • 事务C(80)插入了一条数据,但是A的ReadView也是不变的,所以trx_id>max_trx_id(71),这个数据A读取步到,这样避免了幻读。