http://hi.baidu.com/gao_dennis/item/1f133311f50a94423a176ef5
http://blog.sina.com.cn/s/blog_4673e603010111ty.html
本文主要介绍mysql中innodb引擎undo-log和事务中MVCC多版本一致性读的实现。
1. 概述
Innodb引擎的undo日志是记录在表空间中单独的回滚段中。当mysql做update和delete操作的时候,实际的后台都是先把旧记录“删”了,如果是update和insert再把新记录“插入”进去。
这里的删不是真的删除,而是标识它被删除了。而插入也不一定是真的插入,很多情况下是原地覆盖原来的记录。
而关于MVCC多版本一致性读,就是在同一个事务中,用户只能看到该事务之前已经生效的和该事务本身做的修改。
在innodb中“MVCC多版本一致性读”功能的实现是基于undo-log的。
图1描述了innodb中undo-log和“MVCC多版本一致性读”功能的基本实现。
Innodb引擎的主键索引记录的头上包含有6字节的事务ID(DB_TX_ID)与7字节指向回滚段中旧版本的指针(DB_ROLL_PTR)。
当发生修改的之前,innodb会把被修改的字段的原始版本值和它们对应的版本号写入回滚段中,而指针DB_ROLL_PTR指向的是最新的undo日志。
当一个事务被创建的时候,innodb会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本(readview),副本中保存的是系统当前不应该被本事务看到的其他事务id列表。当用户在这个事务中要读取该行记录的时候,innodb就会从根据read view,来决定是读取该行记录的当前版本,还是需要从undo-log中去寻找更早的版本。
2.undo-log
为了简化分析,关于undo-log本文只介绍如下这种情况,也是最普遍的一种情况:
一条update语句,它根据主键进行查找,并且不修改主键的值。
涉及到的代码入口在 storage/innobase/btr/btr0cur.c 的btr_cur_update_in_place函数(1821行)
首先记录undo-log,把本次修改的字段原始值记录下来:
然后在本条记录上进行修改:
修改后写redo-log,redo-log是单独存放的,存放在名为ib_logfile的一组文件中:
可以看出以上大体的流程就是先写undo-log,然后本地修改,最后写redo-log。
写undo-log的函数btr_cur_upd_lock_and_undo最终会调用函数trx_undo_page_report_modify(或insert)。(storage/innobase/trx/trx0rec.c 529行)。
把旧版本的事务id写入undo-log:
把该行的标识字段写入undo-log:
将旧值保存的undo-log中:
3. MVCC多版本一致性读
在innodb中,创建一个新事务的时候,innodb会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本(readview),副本中保存的是系统当前不应该被本事务看到的其他事务id列表。当用户在这个事务中要读取该行记录的时候,innodb会将该行当前的版本号与该readview进行比较。
具体的算法如下:
1. 设该行的当前事务id为trx_id_0,read view中最早的事务id为trx_id_1,最迟的事务id为trx_id_2。
2. 如果trx_id_0<trx_id_1的话,那么表明该行记录所在的事务已经在本次新事务创建之前就提交了,所以该行记录的当前值是可见的。跳到步骤6.
3.如果trx_id_0>trx_id_2的话,那么表明该行记录所在的事务在本次新事务创建之后才开启,所以该行记录的当前值不可见.跳到步骤5。
4. 如果trx_id_1<=trx_id_0<=trx_id_2,那么表明该行记录所在事务在本次新事务创建的时候处于活动状态,从trx_id_1到trx_id_2进行遍历,如果trx_id_0等于他们之中的某个事务id的话,那么不可见。跳到步骤5.
5.从该行记录的DB_ROLL_PTR指针所指向的回滚段中取出最新的undo-log的版本号,将它赋值该trx_id_0,然后跳到步骤2.
6. 将该可见行的值返回。
关于如何判断当前系统活跃事务列表中的事务是否应该进入read view(即对当前事务不可见),可以参考read view的创建代码:storage/innobase/read/read0read.c的函数read_view_open_now。
可以看出主要是排除了当前事务自己,以及目前正在内存中提交的事务。
关于判断记录当前值是否可见的代码位于storage/innobase/include/read0read.ic的函数read_view_sees_trx_id,图中的view->up_limit_id就是上面的trx_id1,view->low_limit_id就是上面的trx_id2.
下面是关于low_limit_id和up_limit_id的定义(它们的名字看起来容易产生误解。。。):
4. 后记
关于写redo-log的代码入口在:storage/innobase/log/log0log.c的函数log_group_write_buf(1227行)。而关于redo-log相关函数的使用介绍在该文件的开始部分:
个人总结:
即MVCC一致性度是在事务启动时,获取当前活跃事务列表。
然后对该事务中的select来说,其可以读取到在该事务之前已经提交的并且不再活跃事务列表中的的记录。
而对于在其之后提交的,或者在活跃事务列表中的,则需要从其UNDO log中获取一个可用的历史版本。