MySQL源码之ReadView源码分析

当我们执行普通的select查询时,InnoDB会使用MVCC机制来实现记录的无锁一致性读取(non-locking consistent read),也就是在一个事务中对同一条记录多次读取结果保持一致,尽管可能有其他事务在修改该记录。

而MVCC机制主要由两部分来实现,一部分是由undo log组成的版本链,另一部分就是ReadView,本文主要聊聊ReadView的底层实现机制。

关注我,公众号:IT周瑜,后面再聊聊版本链的实现机制。

ReadView有三个重要的属性:

  1. m_low_limit_id,创建ReadView时,事务系统分配给下一个事务的事务id,事务id在源码中用trx_id表示
  2. m_up_limit_id,创建ReadView时,事务系统中所有活跃事务(不包括只读事务)中最小的事务id
  3. m_ids,创建ReadView时,系统中所有活跃事务的事务id

这三个属性控制了ReadView对行记录不同版本的可见性,在遍历版本链时,会返回对当前ReadView可见的第一个版本。

  • 如果某个版本的trx_id >= m_low_limit_id,则表示该版本是本ReadView创建之后才创建的版本,因此对当前ReadView,不可见。
  • 如果某个版本的trx_id < m_low_limit_id,则要判断该版本在本ReadView创建时是否已经提交了
    • 如果某个版本的trx_id < m_up_limit_id,表示已经提交了,可见
    • 如果某个版本的trx_id >= m_up_limit_id,表示未提交:
      • 如果在m_ids中,则表示未提交的活跃事务,不可见
      • 如果不在m_ids中,则表示未提交的不活跃事务,可见(这种情况比较少见,源码对这种情况是可见)

总结一句话:ReadView只对在生成ReadView时已经提交的版本可见,没提交的事务或后续新开启的事务对应的记录版本都是不可见。

另外,ReadView还有一个属性是m_creator_trx_id,表示当前是哪个事务创建了该ReadView,如果一个版本的trx_id == m_creator_trx_id,那就表示该版本是本事务自己创建的,肯定可见。

创建ReadView源码

void
MVCC::view_open(ReadView*& view, trx_t* trx)
{
	ut_ad(!srv_read_only_mode);

	/** If no new RW transaction has been started since the last view
	was created then reuse the the existing view. */
	if (view != NULL) {
		...
	} else {
        // 创建ReadView要加锁,因为需要把系统中当前被激活的所有事务的no复制到ReadView中
		mutex_enter(&trx_sys->mutex); 

        // 会从空闲ReadView链表中获取一个空间的ReadView对象,没有空闲的就会构建一个空的ReadView对象
		view = get_view();
	}

	if (view != NULL) {

        // 初始化m_creator_trx_id、m_low_limit_id、m_ids
		view->prepare(trx->id);

        // 初始化m_up_limit_id,为m_ids中最小的事务id
		view->complete();
        
        // 把view添加到m_views链表中
		UT_LIST_ADD_FIRST(m_views, view);

		ut_ad(!view->is_closed());

		ut_ad(validate());
	}

	trx_sys_mutex_exit();
}
void
ReadView::prepare(trx_id_t id)
{
    ut_ad(mutex_own(&trx_sys->mutex));

    // 哪个事务创建本ReadView
    m_creator_trx_id = id;

    // 创建ReadView时,事务系统下一个要分配的事务id是什么,大于等于该事务id的不可见
    m_low_limit_id = trx_sys->max_trx_id;

    // 把当前系统中的所有活跃的读写事务id复制给m_ids
    if (!trx_sys->rw_trx_ids.empty()) {
        copy_trx_ids(trx_sys->rw_trx_ids);
    } else {
        m_ids.clear();
    }
}
void
ReadView::complete()
{
    /* The first active transaction has the smallest id. */
    // 把m_ids中最小的事务id赋值给m_up_limit_id
    m_up_limit_id = !m_ids.empty() ? m_ids.front() : m_low_limit_id;

    ut_ad(m_up_limit_id <= m_low_limit_id);

    m_closed = false;
}

判断ReadView可见性

bool changes_visible(
trx_id_t		id,
const table_name_t&	name) const
MY_ATTRIBUTE((warn_unused_result))
{
    ut_ad(id > 0);

    // 如果记录的事务id小于m_up_limit_id,则表示该记录是在ReadView创建之前就已经提交的,所以可见
    // 如果记录的事务id等于m_creator_trx_id,则表示该记录和ReadView是同一个事务,所以可见
    if (id < m_up_limit_id || id == m_creator_trx_id) {

        return(true);
    }

    // 如果记录的事务id大于等于m_low_limit_id,则表示该记录大于等于ReadView中的所有事务id
    // 也就表示该记录是创建ReadView之后的其他事务修改的,因此对当前ReadView不可见
    if (id >= m_low_limit_id) {

        return(false);

    } else if (m_ids.empty()) {
        // 如果m_ids是空的,表示创建ReadView时没有其他记录,并且id<m_low_limit_id,也是可见的
        return(true);
    }

    const ids_t::value_type*	p = m_ids.data();

    // 在m_ids中进行查找,找到了就不可见,没找到就可见,m_ids表示活跃的事务,如果记录的事务id不属于ReadView中活跃的事务id,则可见
    return(!std::binary_search(p, p + m_ids.size(), id));
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值