mysql 多版本一致性读_InnoDB的多版本一致性读的实现

InnoDB是支持MVCC多版本一致性读的,因此和其他实现了MVCC的系统如Oracle,PostgreSQL一样,读不会阻塞写,写也不会阻塞读。虽然同样是MVCC,各家的实现是不太一样的。Oracle通过在block头部的事务列表,和记录中的锁标志位,加上回滚段,个人认为实现上是最优雅的方式。 而PostgreSQL则更是将多个版本的数据都放在表中,而没有单独的回滚段,导致的一个结果是回滚非常快,却付出了查询性能降低的代价。

InnoDB的实现更像Oracle,同样是将数据的旧版本存放在单独的回滚段中,但是也有不同。之前还以为整体实现都会跟Oracle不会有太大的出入,也一直没有太在意去看具体实现。今晚晚上下班准备回家时,刚好路过几个同事在交流分享这个问题,遇到一个疑问:

我们知道,InnoDB表会有三个隐藏字段,6字节的DB_ROW_ID,6字节的DB_TX_ID,7字节的DB_ROLL_PTR(指向对应回滚段的地址),这个可以通过innodb monitor看到,当然如果你熟悉innodb文件结构,也可以直接od ibd文件来验证。一致性读主要跟后两者有关系。InnoDB内部维护了一个递增的tx id counter,其当前值可以通过show engine innodb status获得

echo "show engine innodb status\G" | mysql -uroot | grep "Trx id counter"

假设有一个表,当前已经有两条记录。这时候我们开始一个实验,开启两个session,A和B,都设置autocommit=0

MySQL>set global autocommit=0;

T1时间:

A开始一个事务,执行一条select,可以看到已有的两条记录,show engine innodb status可以知道A的tx id,假设为7430

T2时间:

B开始一个事务,执行一条select,可以看到已有的两条记录,可以知道B的tx id,为7431

T3时间:

A中insert一条记录,此时A再select能看到,所以返回三条记录,而B无法看到,还是返回两条记录。

T4时间:

A中执行commit提交事务,分别在A和B中select,得到的结果和T3时间相同。

Ok,假设一致性读是根据事务先后,也就是tx id来比较的话,如果B事务的一致性读是通过B的tx id即7431来和A事务中insert的这条记录的tx id即7430来比较的话,由于A.tx_id < B.tx_id,那么B应该能都到A的记录(tx id是递增的,所以越小说明事务越早开始),但如果能读到,则显然不符合多版本一致性。

因此结果是正确的,那么就是InnoDB的一致性读的实现方式不是像我们按照经验来测试的那样了。通过google和察看代码,原来InnoDB还真是有一个感觉上很山寨的设计,由于tx id是事务一开始就分配的,事务中的变化也没有记录一个类似于Oracle的SCN的逻辑时钟,于是由了如下的实现:

InnoDB每个事务在开始的时候,会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本(read view),然后一致性读去比较记录的tx id的时候,并不是根据当前事务的tx id,而是根据read view最早一个事务的tx id(read view->up_limit_id)来做比较的,这样就能确保在事务B之前没有提交的所有事务的变更,B事务都是看不到的。当然,这里还有个小问题要处理一下,就是当前事务自身的变更还是需要看到的。

有兴趣的可以去仔细看看代码的实现,在storage/innobase/read/read0read.c中实现了创建read view的函数read_view_open_now,在storage/innobase/include/read0read.ic中实现了判断一致性读是否可见的read_view_sees_trx_id。以下代码摘自5.5.8:

UNIV_INTERN

read_view_t*

read_view_open_now(

/*===============*/

trx_id_tcr_trx_id,/*!< in: trx_id of creating

transaction, or 0 used in purge */

mem_heap_t*heap)/*!< in: memory heap from which

allocated */

{

read_view_t*view;

trx_t*trx;

ulintn;

ut_ad(mutex_own(&kernel_mutex));

view = read_view_create_low(UT_LIST_GET_LEN(trx_sys->trx_list), heap);

view->creator_trx_id = cr_trx_id;

view->type = VIEW_NORMAL;

view->undo_no = 0;

/* No future transactions should be visible in the view */

view->low_limit_no = trx_sys->max_trx_id;

view->low_limit_id = view->low_limit_no;

n = 0;

trx = UT_LIST_GET_FIRST(trx_sys->trx_list);

/* No active transaction should be visible, except cr_trx */

while (trx) {

if (trx->id != cr_trx_id

&& (trx->conc_state == TRX_ACTIVE

|| trx->conc_state == TRX_PREPARED)) {

read_view_set_nth_trx_id(view, n, trx->id);

n++;

/* NOTE that a transaction whose trx number is <

trx_sys->max_trx_id can still be active, if it is

in the middle of its commit! Note that when a

transaction starts, we initialize trx->no to

IB_ULONGLONG_MAX. */

if (view->low_limit_no > trx->no) {

view->low_limit_no = trx->no;

}

}

trx = UT_LIST_GET_NEXT(trx_list, trx);

}

view->n_trx_ids = n;

if (n > 0) {

/* The last active transaction has the smallest id: */

view->up_limit_id = read_view_get_nth_trx_id(view, n - 1);

} else {

view->up_limit_id = view->low_limit_id;

}

UT_LIST_ADD_FIRST(view_list, trx_sys->view_list, view);

return(view);

}

UNIV_INLINE

ibool

read_view_sees_trx_id(

/*==================*/

const read_view_t*view,/*!< in: read view */

trx_id_ttrx_id)/*!< in: trx id */

{

ulintn_ids;

ulinti;

if (trx_id < view->up_limit_id) {

return(TRUE);

}

if (trx_id >= view->low_limit_id) {

return(FALSE);

}

/* We go through the trx ids in the array smallest first: this order

may save CPU time, because if there was a very long running

transaction in the trx id array, its trx id is looked at first, and

the first two comparisons may well decide the visibility of trx_id. */

n_ids = view->n_trx_ids;

for (i = 0; i < n_ids; i++) {

trx_id_tview_trx_id

= read_view_get_nth_trx_id(view, n_ids - i - 1);

if (trx_id <= view_trx_id) {

return(trx_id != view_trx_id);

}

}

return(TRUE);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值