mysql innodb mvcc 读一致性(Repeatable Read)通俗笔记

InnoDB 的 MVCC 和oracle 还是有区别,没有oracle那么纯粹,很简单可以体现在oracle 我可以直接flashback查询,但是InnoDB不行。 oracle 是怎么做MVCC 就没有具体了解了,快2年没有用oracle了,肯定是undo log ,redo log 等结构更强大。


InnoDB MVCC提供了两个关键功能,一:写不阻塞读 。  二:读一致性。一下主要介绍一下InnoDB实现读一致性需要达到的效果大家容易理解,要实现Repeatable Read 事务隔离级别,就是InnoDB实现到什么程度了,我感觉开发人员比较容易糊涂,我也是。

    附:写不阻塞读,个人感觉只要是MVCC的,肯定就是写不阻塞读了,MVCC给我们程序员提供的最好东西也就是写不阻塞读,不过Mysql InnoDB 在某些情况下写会阻塞读,下文会写到。

InnoDB engine 有一个全局的 Transaction ID (事务ID,下文用trx_id 表示,即当前版本)  使用show engine innodb status 可以看到。

写在前面:以下描述过于繁杂,我表述也成问题,如果把 事务ID 当版本ID 来读就更好理解。

每开启一个事务的时候 trx_id 就会自增保证每个事务都有自己的trx_id,事务内多步操作不会每个操作都增长(废话) 。当 autocommit = 1  的时候 每个select 都会让trx_id 涨一,这时 trx_id 的增长当会受查询缓存影响,如查询两次没有涨那肯定就是查询缓存起作用了,连续两个select 查询结果一样,后一个是读得查询缓存。

我得理解:每个条数据都记录了一个事务id,就是该数据最后被一次修改的事务ID。

再稍微提下一undo log,redo log。网上一找一堆,我把自己认为合理的说一下,这个undo log和读一致性有关,有必要提一下:
来个找到的定义,写得比较好就直接贴出来:
redo log:重做日志,就是每次mysql在执行写入数据前先把要写的信息保存在重写日志中,但出现断电,奔溃,重启等等导致数据不能正常写入期望数据时,服务器可以通过redo_log中的信息重新写入数据。
undo log:撤销日志,与redo log恰恰相反,当一些更改在执行一半时,发生意外,而无法完成,则可以根据撤消日志恢复到更改之前的壮态。
以一个update 操作为例子:
首先记录undo-log,把本次修改的字段原始值记录下来(包括旧版本的事务id,即修改前的事务id,读一致性里面这个比较重要)
然后在本条记录上进行修改(具体看参考文献)(映像oracle 是复制一条新的记录,标记为update操作的版本号,可能这就是oracle的MVCC那么纯粹,可以flashbak 查询而mysql InnoDB 不行的原因,oracle查询带着版本号就行,我猜mysql 就得一层层的查redo-log,性能上面肯定就不行,这个功能也就做不了了)
修改后写redo-log(包括有新版本事务id)
接着就commit;

undo redo log 说了就说 关于InnoDB如何实现MVCC 读一致性的:

每个select 都会产生一个 read view
在事务开启的时候创建一个记录之前已经是活跃的事务(还没有提交的事务)trx_id 列表,这个就是 read view,在事务结束前是不会变的,代表着当前的版本!

设其中最早的事务id为trx_id_low,最迟的事务id为trx_id_up
本操作所在的事务id(版本号)w为 trx_id_cur

首先:这个trx_id_cur 肯定大于trx_id_up ,版本号是唯一的,递增的。

查询的时候,查到当行的记录的trx_id 为 trx_id_row 。

首先这个trx_id_row 可能比trx_id_cur大,那就比trx_id_up 也大了,场景:
    读的时候其他线程又新起了一个事务,插入了一条数据还commit了。版本号就比当前操作大了,这种数据肯定不应该可见,不然读到后面版本提交的数据那何来MVCC。
以上就是很多博客直接说的
条件1 trx_id_row  > trx_id_up(不是很好理解),但是算法就是这样的(由于不可见到条件5去查undo log)

条件2 如果trx_id_row < trx_id_low  由于trx_id_low < trx_id_up < trx_id_cur  所以说明该行数据在本次事务开始已经提交了,所以可见,直接返回到结果集。

条件3 如果  trx_id_low  <= trx_id_row  <= trx_id_up
就应该遍历对当前  read view 的 list
     如果包含trx_id_row,说明当前事物开始的时候,这个事物还没有提交,现在提交了肯定对与当前事物来说不可见,毕竟当前事物开始的时候它还没有被提交(到条件5去查undo log)。
     如果不包含就没有问题了,因为trx_id_row  < trx_id_up  <  trx_id_cur , 比当前版本小,又是提交了的,就是可见的了(这个可以理解吧),直接加入返回结果集了。

条件5 对于所有不可见的 trx_id_row 就通过 该行主键(应该是oracle ROW_ID 那种)去查undo_log ,找到undo log 中最新一个版本,把它的trx_id 赋值给当前的trx_id_row  再跑一次判断,直到满足条件就返回。为什么需要循环跑?就是条件1的极端情况,本事务启动后,先后有两个事务修改过当前行的数据,最新一条redo log 的事务id 都 > trx_id_cur > trx_id_up 肯定需要再循环判断,找到具体需要的版本 ,这时一个类似链式的过程(当然具体数据结构我不知道,再次验证innoDB 不能带版本查询的原因了)

我觉得有点需要提(由于这个在源码剖析的方法外面,别人没有提):trx_id_row = trx_id_cur 的时候是本事务内的操作,对本事务肯定可见了。

完。

关于一直说mysql 不能按版本查询 ,一般oracle 的flashback 查询都是程序员犯错的一个补救措施,回滚数据。不直接,说两个oracle 可以但是mysql不行的查询场景更直观:
1、写操作依赖本表数据,造2个例子(不恰当再换) :
a例子:update table_a  a set a_col1 = (select a_col2 from table_a aa where a.col3 = aa.col3 + 1 )(oracle 就可以,这才是真MVCC)
b例子:insert into a select * from a;
2、a 表有写操作未提交,使用a表被操作加锁的行去更新b表会被锁(又不算真MVCC,oracle又可以):例子:
事务1:
update a set col1 = ? where fk  =1;
事务2
update b set cols = (select sum(col1) from a where a.fk = b.id)

事务1没有提交,事务2会阻塞,这其实就是写阻塞读了。

当然这些缺点我们可以在应用层(java)中自己处理,无伤大雅。

具体参考了别人的源码剖析:http://hi.baidu.com/gao_dennis/item/1f133311f50a94423a176ef5

我就是用一个更直接的话表述出来了,当然我得表述总是不清楚,轻拍!

你可能感兴趣的:(mysql,InnoDB,mvcc)