mtr_commit,至此,在文件层次事务提交。这个时候即使crash,重启后依然能保证事务是被提交的。接下来要做的是内存数据状态的更新(trx_commit_in_memory)。
如果是只读事务,则只需要把readview从全局readview链表中移除,然后重置trx_t结构体里面的信息即可。如果是读写事务,情况则复杂点,首先需要是设置事务状态为TRX_STATE_COMMITTED_IN_MEMORY,其次,释放所有行锁,接着,trx_t从rw_trx_list中移除,readview从全局readview链表中移除,另外如果有insert undo则在这里移除(update undo在事务提交前就被移除,主要是为了保证添加到history list的顺序),如果有update undo,则唤醒Purge线程进行垃圾清理,最后重置trx_t里的信息,便于下一个事务使用。
事务的回滚InnoDB的事务回滚是通过undolog来逆向操作来实现的,但是undolog是存在undopage中,undopage跟普通的数据页一样,遵循bufferpool的淘汰机制,如果一个事务中的很多undopage已经被淘汰出内存了,那么在回滚的时候需要重新把这些undopage从磁盘中捞上来,这会造成大量io,需要注意。此外,由于引入了savepoint的概念,事务不仅可以全部回滚,也可以回滚到某个指定点。
回滚的上层函数是innobase_rollback_trx,主要流程如下:
如果是只读事务,则直接返回。
判断当前是回滚整个事务还是部分事务,如果是部分事务,则记录下需要保留多少个undolog,多余的都回滚掉,如果是全部回滚,则记录0(trx_rollback_step)。
从update undo和insert undo中找出最后一条undo,从这条undo开始回滚(trx_roll_pop_top_rec_of_trx)。
如果是update undo则调用row_undo_mod进行回滚,标记删除的记录清理标记,更新过的数据回滚到最老的版本。如果是insert undo则调用row_undo_ins进行回滚,插入操作,直接删除聚集索引和二级索引。
如果是在奔溃恢复阶段且需要回滚的undolog个数大于1000条,则输出进度。
如果所有undo都已经被回滚或者回滚到了指定的undo,则停止,并且调用函数trx_roll_try_truncate把undolog删除(由于不需要使用unod构建历史版本,所以不需要留给Purge线程)。
此外,需要注意的是,回滚的代码由于是嵌入在query graphy的框架中,因此有些入口函数不太好找。例如,确定回滚范围的是在函数trx_rollback_step中,真正回滚的操作是在函数row_undo_step中,两者都是在函数que_thr_step被调用。
数据库需要做好版本控制,防止不该被事务看到的数据(例如还没提交的事务修改的数据)被看到。在InnoDB中,主要是通过使用readview的技术来实现判断。查询出来的每一行记录,都会用readview来判断一下当前这行是否可以被当前事务看到,如果可以,则输出,否则就利用undolog来构建历史版本,再进行判断,知道记录构建到最老的版本或者可见性条件满足。
在trx_sys中,一直维护这一个全局的活跃的读写事务id(trx_sys->descriptors),id按照从小到大排序,表示在某个时间点,数据库中所有的活跃(已经开始但还没提交)的读写(必须是读写事务,只读事务不包含在内)事务。当需要一个一致性读的时候(即创建新的readview时),会把全局读写事务id拷贝一份到readview本地(read_view_t->descriptors),当做当前事务的快照。read_view_t->up_limit_id是read_view_t->descriptors这数组中最小的值,read_view_t->low_limit_id是创建readview时的max_trx_id,即一定大于read_view_t->descriptors中的最大值。当查询出一条记录后(记录上有一个trx_id,表示这条记录最后被修改时的事务id),可见性判断的逻辑如下(lock_clust_rec_cons_read_sees):
如果记录上的trx_id小于read_view_t->up_limit_id,则说明这条记录的最后修改在readview创建之前,因此这条记录可以被看见。
如果记录上的trx_id大于等于read_view_t->low_limit_id,则说明这条记录的最后修改在readview创建之后,因此这条记录肯定不可以被看家。
如果记录上的trx_id在up_limit_id和low_limit_id之间,且trx_id在read_view_t->descriptors之中,则表示这条记录的最后修改是在readview创建之时,被另外一个活跃事务所修改,所以这条记录也不可以被看见。如果trx_id不在read_view_t->descriptors之中,则表示这条记录的最后修改在readview创建之前,所以可以看到。