(1) m_ids: Read View创建时其他未提交的活跃事务ID列表。具体说来就是创建Read View时,将当前未提交事务ID记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。注意:该事务ID列表不包括当前事务自己和已提交的事务。
(2) m_low_limit_id:某行数据的DB_TRX_ID >= m_low_limit_id的任何版本对该查询不可见。那么这个值是怎么确定的呢?其实就是读的时刻出现过的最大的事务ID+1,即下一个将被分配的事务ID。见https://github.com/mysql/mysql-server/blob/5.7/storage/innobase/read/read0read.cc#L459
/** Opens a read view where exactly the transactions serialized before this point in time are seen in the view. @param id Creator transaction id */ void ReadView::prepare(trx_id_t id) { m_creator_trx_id = id; m_low_limit_no = m_low_limit_id = trx_sys->max_trx_id; }max_trx_id见https://github.com/mysql/mysql-server/blob/5.7/storage/innobase/include/trx0sys.h#L576中的描述,翻译过来就是“还未分配的最小事务ID”,也就是下一个将被分配的事务ID。(注意,m_low_limit_id并不是活跃事务列表中最大的事务ID)
struct trx_sys_t { /*!< The smallest number not yet assigned as a transaction id or transaction number. This is declared volatile because it can be accessed without holding any mutex during AC-NL-RO view creation. */ volatile trx_id_t max_trx_id; }(3) m_up_limit_id:某行数据的DB_TRX_ID < m_up_limit_id的所有版本对该查询可见。同样这个值又是如何确定的呢?m_up_limit_id是活跃事务列表m_ids中最小的事务ID,如果trx_ids为空,则m_up_limit_id为m_low_limit_id。代码见https://github.com/mysql/mysql-server/blob/5.7/storage/innobase/read/read0read.cc#L485
void ReadView::complete() { /* The first active transaction has the smallest 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; }这样就有下面的可见性比较算法了。代码见https://github.com/mysql/mysql-server/blob/5.7/storage/innobase/include/read0types.h#L169
/** 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); /* 假如 trx_id 小于 Read view 限制的最小活跃事务ID m_up_limit_id 或者等于正在创建的事务ID m_creator_trx_id * 即满足事务的可见性. */ if (id < m_up_limit_id || id == m_creator_trx_id) { return(true); } /* 检查 trx_id 是否有效. */ check_trx_id_sanity(id, name); if (id >= m_low_limit_id) { /* 假如 trx_id 大于等于m_low_limit_id, 即不可见. */ return(false); } else if (m_ids.empty()) { /* 假如目前不存在活跃的事务,即可见. */ return(true); } const ids_t::value_type* p = m_ids.data(); /* 利用二分查找搜索活跃事务列表 * 当 trx_id 在 m_up_limit_id 和 m_low_limit_id 之间 * 如果 id 在 m_ids 数组中, 表明 ReadView 创建时候,事务处于活跃状态,因此记录不可见. */ return (!std::binary_search(p, p + m_ids.size(), id)); }完整梳理一下整个过程。
在InnoDB中,创建一个新事务后,执行第一个select语句的时候,InnoDB会创建一个快照(read view),快照中会保存系统当前不应该被本事务看到的其他活跃事务id列表(即m_ids)。当用户在这个事务中要读取某个记录行的时候,InnoDB会将该记录行的DB_TRX_ID与该Read View中的一些变量进行比较,判断是否满足可见性条件。
假设当前事务要读取某一个记录行,该记录行的DB_TRX_ID(即最新修改该行的事务ID)为trx_id,Read View的活跃事务列表m_ids中最早的事务ID为m_up_limit_id,将在生成这个Read Vew时系统出现过的最大的事务ID+1记为m_low_limit_id(即还未分配的事务ID)。
具体的比较算法如下: