本文不是从头介绍,需要读者先看完其它参考文章,对各种专有名词有一个概念。
mysql 8.0.13源码
...
|-index_read
|-row_search_mvcc
index_read有一个分支会调用row_search_no_mvcc,但这个只在表是intrinsic时才调用。intrinsic表示mysql的一个内部用的表,我们不用管它。 我们看下row_search_mvcc里的一个分支:
// 这边只有select不加锁模式的时候才会创建一致性视图
else if (prebuilt->select_lock_type == LOCK_NONE) { // 创建一致性视图
trx_assign_read_view(trx);
prebuilt->sql_stat_start = FALSE;
}
上面的注释就是select for update(in share model)不会走MVCC的原因。让我们进一步分析trx_assign_read_view函数:
trx_assign_read_view
|-MVCC::view_open
|-MVCC::get_view
|-ReadView::prepare
当view为空时,会调用view = get_view();
得到一个空闲ReadView,然后调用view->prepare(trx->id);
对其初始化
void ReadView::prepare(trx_id_t id) {
// ut_ad的作用相当于assert,说明现在一定获取了事务系统的锁
ut_ad(mutex_own(&trx_sys->mutex));
// m_creator_trx_id: 创建该ReadView的事务的id
m_creator_trx_id = id;
// trx_sys->max_trx_id: 还未分配的最小事务id
m_low_limit_no = m_low_limit_id = m_up_limit_id = trx_sys->max_trx_id;
// 保存当前的事务id快照
// rw_trx_ids: 当前的事务,可能是ACTIVE或PREPARED状态
if (!trx_sys->rw_trx_ids.empty()) {
copy_trx_ids(trx_sys->rw_trx_ids); // 函数内,把rw_trx_ids拷贝一份到m_ids,但自己的事务id除外。还设置了m_up_limit_id为m_ids首元素,也就是当前最小id。
} else {
m_ids.clear();
}
// ut_ad的作用如上,现在一定m_up_limit_id <= m_low_limit_id
// 此时,m_low_limit_id是还未分配的最小事务id,m_up_limit_id是当前存在的事务的最小id。
// 比如这样的情况: [存在的事务id1(也就是m_up_limit_id), 存在的事务id2, (不包括m_creator_trx_id), ... ,最大的事务id], m_low_limit_id(还未被分配)
// 其中,中括号以内就是m_ids,m_ids不包括m_creator_trx_id
ut_ad(m_up_limit_id <= m_low_limit_id);
if (UT_LIST_GET_LEN(trx_sys->serialisation_list) > 0) {
const trx_t *trx;
trx = UT_LIST_GET_FIRST(trx_sys->serialisation_list);
if (trx->no < m_low_limit_no) {
m_low_limit_no = trx->no;
}
}
// 走到这里,上限m_low_limit_id是trx_sys->max_trx_id,下限m_up_limit_id是m_ids首元素,
m_closed = false;
}
ReadView::changes_visible:
/** Check whether the changes by id are visible.
@param[in] id transaction id to check against the view
@param[in] name table name
@return whether the view sees the modifications of id. */
bool changes_visible(trx_id_t id, const table_name_t &name) const
MY_ATTRIBUTE((warn_unused_result)) {
ut_ad(id > 0);
// 若记录的id小于下限,或就是记录的id就是事务的id自己,就都可见
if (id < m_up_limit_id || id == m_creator_trx_id) {
return (true);
}
check_trx_id_sanity(id, name);
// 大于等于上限的id都不可见,因为m_low_limit_id等于trx_sys->max_trx_id,也就是还未分配的最小id。
if (id >= m_low_limit_id) {
return (false);
} else if (m_ids.empty()) {
return (true);
}
// 其实p就是m_ids的首元素的指针,m_ids是一个vector。
const ids_t::value_type *p = m_ids.data();
// 运行到这里,我们可以肯定id小于上限,而上限是还未分配的最小id,所以id一定属于某个事务,只是这个事务可能存在,也可能已经提交。
// 用二分查找,在m_ids里找这个id,m_ids里的id在创建时都没有提交,因此都是不可见的。如果找得到,说明不可见。
return (!std::binary_search(p, p + m_ids.size(), id));
}
m_ids
,对于最新的m_ids
,最新的m_ids
里的id在被过滤出来时都没有提交,因此都是不可见的。m_ids
,此时没提交的事务,在之后就算提交了,也不能可见。m_ids
时被过滤掉了,但都一定小于上限。trx_sys->max_trx_id
。// row0sel.cc row_search_mvcc
else if (prebuilt->select_lock_type == LOCK_NONE) {
/* This is a consistent read */
/* Assign a read view for the query */
if (!srv_read_only_mode) {
trx_assign_read_view(trx);
}
prebuilt->sql_stat_start = FALSE;
} else {
...
prebuilt->select_lock_type == LOCK_NONE
明确讲了,只有无锁情况下才会获取视图。而update
、delete
、select for update
都是要锁的,不会触发视图的获取。
// trx0trx.cc
/** Assigns a read view for a consistent read query. All the consistent reads
within the same transaction will get the same read view, which is created
when this function is first called for a new started transaction.
@return consistent read view */
ReadView *trx_assign_read_view(trx_t *trx) /*!< in/out: active transaction */
{
ut_ad(trx->state == TRX_STATE_ACTIVE);
if (srv_read_only_mode) {
ut_ad(trx->read_view == NULL);
return (NULL);
} else if (!MVCC::is_view_active(trx->read_view)) {
trx_sys->mvcc->view_open(trx->read_view, trx);
}
return (trx->read_view);
}
所以,RR是在第一次select时才创建视图。(原文中提到了trx->global_read_view
,但在我的版本的代码里没有找到,但不影响理解逻辑。)