InnoDB MVCC

MVCC:
    multiversion concurrency control, 多版本并发控制,它提供基于某个时间的快照,使得对于事务看来,总是可以提供与事务开始时刻相一致的数据。而不管这个事务执行的时间有多长,故在不同事务看来,同一时刻看到的相同的行数据可能是不一样的,即:每一行数据会有多个版本数据(副本)。


InnoDB的实现:
InnoDB的MVCC主要是根据undo log来实现的。
在InnoDB的记录格式中都有两个字段来记录一行的版本(DATA_TRX_ID)和上一个版本在undo log中的位置(DATA_ROLL_PTR)。
DATA_TRX_ID是一个在全系统中递增的值,每创建一个mini-transaction就递增一,每一个mini-transaction commit时(状态变为COMMITTED_IN_MEMORY)时,对于是update操作的也需要递增一(这个事务ID会赋值给trx->no,用于表示事务提交的顺序)。

DATA_ROLL_PTR记录了回滚的版本在undo log中的位置,DATA_ROLL_PTR的结构如下:
bits        55: 1 INSERT        |              54 ~48        |                         47 ~ 16               |                    0 ~ 15                                                |
         ------------------------------------------------------------------------------------------------------------------------------- --------------
          |INSERT or UPDATE | rollback segment ID | undo log segment page number | undo log record offset in undo log segment page |

Read View:
InnoDB默认的隔离级别为Repeatable Read (RR: TRX_ISO_REPEATABLE_READ),可重复读。InnoDB在开始一个RR读之前都会创建一个Read View。

struct read_view_struct{

 trx_id_t low_limit_no;             /*创建view时, 当前活跃事务中正处在COMMITTED_IN_MEMORY状态 的最小的trx->no, 如果没有则该值是trx_sys->max_trx_id 见函数read_view_open_now*/

 trx_id_t low_limit_id;           /* 事务号 >= low_limit_id的记录,对于当前Read View都是不可见的; 这个值一般是创建VIEW时当前最大的TRX_ID,即trx_sys->max_trx_id; 也就是说TRX_ID大于low_limit_id的事务都是在本VIEW的事务开始之后才开始的, 因此对本事务来说是不可见的 */ 

 trx_id_t up_limit_i d;             /* 事务号 < up_limit_id ,对于当前Read View都是可见的; 因为up_limit_id是数组trx_ids中TRX_ID最小的值(见read_view_open_now), 也就是说小于up_limit_id的事务都已经提交了,因此TRX_ID小于up_limit_id的事务对当前的VIEW都是可见的*/ 
 
ulint n_trx_ids;                     /* 即下面trx_ids数组的元组个数*/ 
 
trx_id_t* trx_ids;                  /* 所有创建view时还活跃的事务的TRX_ID数组, 这些值是介于up_limit_id和low_limit_id之间, 这个数组是按TRX_ID从大到小排列的, 即最后一个最小, 见函数read_view_open_now */
 
trx_id_t creator_trx_id;         /*    创建这个view的trx的trx_id, 见函数read_view_open_now*/
 
UT_LIST_NODE_T(read_view_t) view_list;     /*将本view加入到双向链表trx_sys->view_list中的链表节点 */
};

判断一个事务对一个Read View是否可见的算法:

read_view_sees_trx_id( const read_view_t* view, /*!< in: read view */  trx_id_t trx_id) /*!< in: trx id */

1.  如果trx_id小于 view->up_limit_id,则这个事务对这个view是可见的,因为 view->up_limit_id是所有活跃事务中事务ID最小的事务,小于这个ID的事务在创建VIEW的时候都已经提交了,所以对当前的VIEW是可见的。
2.  如果 trx_id大于view->low_limit_id,则这个事务对这个view是不可见的,因为 view->low_limit_id是创建VIEW时的trx_sys->max_trx_id,也就是说,这个事务在创建VIEW的时候还没有开始,因此对当前的VIEW是不可见的。
3. 对于介于 view->up_limit_id view->low_limit_id 之间的trx_id, 查看这个trx_id是否在数组 view-> trx_ids 中, 如果在这个数组中,则表示这个事务对当前VIEW是不可见的,因为这些事务在创建VIEW的时候没有commit。否则表示这个事务在创建VIEW的时候已经commit, 因此对这个VIEW来说是可见的。


创建历史版本:
对于当前查到的记录检查对当前的事务是否可见,如果不可见,就用记录中的DATA_ROLL_PTR取查找undo log中的undo log record, 如果这个undo log record记录的是INSERT, 则表示这个记录没有更老的版本了,这个记录的历史版本中没有当前事务可见的版本。如果是UPDATE的undo log record,则用当前的记录和undo log record记录的内容构造出前一个历史版本的record。再查看这里历史版本的TRX_ID对当前的事务是否可见,如果可见则返回这个版本。如果还是不可见,则用相似的方法构造更老的版本的记录,直到记录对当前事务可见或者到达了最老的版本还是不可见,则返回NULL。


辅助索引的查找:

        InnoDB的页面上都有一个PAGE_MAX_TRX_ID项,记录了修改当前页的最大的TRX_ID. 如果PAGE_MAX_TRX_ID小于 read_view.up_limit_id (lock_sec_rec_cons_read_sees),  说明当前的VIEW对该页上的所有记录都是可见的,就可以在当前页上进行扫描查找符合条件的记录了。
        如果PAGE_MAX_TRX_ID不小于read_view.up_limit_id, 则需要用辅助索引中的记录去主键索引中查找相应的记录(row_sel_get_clust_rec_for_mysql)。
         查询到的锁的类型不是non-locking consistent读,则检查锁的一致性(lock_clust_rec_read_check_and_lock)看是否能立即读。
         如果查询的类型是non-locking consistent读,检查记录是否对当前的VIEW可见,如果不可见,则需要构造历史版本(row_sel_build_prev_vers_for_mysql)。查询主键索引获取记录后还要检查获取的是否是正确的,因为存在这样的情况, 在辅助索引中可能两个辅助索引的记录(其中一个可能标记为删除)对应的主键相同, 而主键可能是历史版本的, 索引要查找那个与历史版本的主键相同的记录。如下图所示,二级索引中有两项pk = 5 (一项deleted bit = 1,另一个为0),对应的聚簇索引中只有一项pk= 5。如何保证通过二级索引过来的同一记录的多个版本,在聚簇索引中最多只能被返回一次?如果当前事务id 1010可见。二级索引pk = 5的记录(两项),通过聚簇索引的undo,都定位到了同一记录项。此时,InnoDB通过以下的一个表达式,来保证来自二级索引,指向同一聚簇索引记录的多个版本项,有且最多仅有一个版本将会返回数据:

 if (clust_rec                                              //需要回聚簇索引扫描,并且获得记录 存在
                && (old_vers                           //聚簇索引记录为回滚版本
                    || trx->isolation_level <= TRX_ISO_READ_UNCOMMITTED
                    || rec_get_deleted_flag(rec, dict_table_is_comp(
                                                sec_index->table)))  //二级索引中的记录为删除版本
                && !row_sel_sec_rec_is_for_clust_rec(
                    rec, sec_index, clust_rec, clust_index))  //辅助索引中的列中的内容与主键索引中的相应列的内容不相同
        {
            clust_rec = NULL;
#ifdef UNIV_SEARCH_DEBUG
        }

row_sel_sec_rec_is_for_clust_rec:比较辅助索引中的列中的内容与主键索引中的相应列的内容是否相同,即辅助索引现在记录的内容是否和还是现在查到的主键(或历史版本)的内容相同。即辅助索引的记录和主键的记录是否为相同的VERSION.




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值