可重复读的实现
可重复读隔离级别,简称RR,在2.1.1节,我们说过:
Repeatable Read(可重复读):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录(读已经提交的,其实是读早于本事务开始且已经提交的),但是不能看到其他事务对已有记录的更新(即晚于本事务开始的),并且,该事务不要求与其他事务是“可串行化”的。
这句话的核心,是“但是不能看到其他事务对已有记录的更新”,那么RR隔离级别是怎么保证这一点的呢?
如果数据库并发控制引擎是单纯的封锁协议机制,则应该在读取数据的时候,判断数据项是不是其他事务更新过的。可是InnoDB没有这么做,而是通过如下方式,在RR隔离级别下为事务设置了一个“一致性读视图(即快照)”,之后读取数据,就是根据这个快照来获取,这样,就不能看到他晚于本事务的事务对已有记录的更新(更新生成新版本,必然不在旧的快照所限定的范围内)。
static my_bool snapshot_handlerton(THD *thd, plugin_ref plugin, void *arg)
{
handlerton *hton= plugin_da
if (hton->state == SHOW_OPTION_YES && hton->start_consistent_snapshot) //隔离级别是RR时start_consistent_snapshot才被赋值
{
hton->start_consistent_snapshot(hton, thd); //对于InnoDB,实际执行innobase_start_trx_and_assign_read_view()函数
*((bool *)arg)= false;
}
return FALSE;
}
如图11-4和下面的代码分析,在事务开始的时候trans_begin()会调用snapshot_handlerton()函数指针即使用innobase_start_trx_and_assign_read_view()函数在可重复读隔离级别下创建一个快照,其他隔离级别则不创建快照。
![InnoDB---可重复读隔离级别的实现 - 那海蓝蓝 - 那海蓝蓝的博客 InnoDB---可重复读隔离级别的实现 - 那海蓝蓝 - 那海蓝蓝的博客](http://img0.ph.126.net/-QJ9AxxF_4Afjwc5B9D33w==/6632080415910706170.png)
图11-4 snapshot_handlerton()函数上下文调用关系图
innobase_start_trx_and_assign_read_view( //在可重复读隔离级别下创建一个快照,其他隔离级别则不创建快照
handlerton* hton, /*!< in: InnoDB handlerton */
THD* thd) /*!< in: MySQL thread handle of the user for whom the transaction should be committed */
{...
if (trx->isolation_level == TRX_ISO_REPEATABLE_READ) { //如果是RR隔离级别,则给read view赋值,即构建一致性视图
trx_assign_read_view(trx); //为读一致性视图(快照)赋一个值,注意在store_lock()中应隔离级别小于RR才关闭快照
} else {
push_warning_printf(thd, Sql_condition::SL_WARNING,
HA_ERR_UNSUPPORTED,
"InnoDB: WITH CONSISTENT SNAPSHOT"
" was ignored because this phrase"
" can on
" REPEATABLE READ isolation level.");
}
...
}
之后,在每条SQL语句执行的时候,根据隔离级别判断是不是要使用一个新的快照,如果是可重复读,则不使用新快照,沿用老的快照,这样就能保证所有的读操作看到的是同一个数据状态;同时也确保了读已提交隔离级别下一个事务块内的不同语句的读操作看到的不是同一个数据状态。
ha_innobase::store_lock(...)
{...
if (lock_type != TL_IGNORE && trx->n_mysql_tables_in_use == 0) {
trx->isolation_level = innobase_map_isolation_level((enum_tx_isolation) thd_tx_isolation(thd));
if (trx->isolation_level <= TRX_ISO_READ_COMMITTED //隔离级别小于等于读已提交,关闭老的快照。可重复读不关闭老快照所以可以沿用
&& MVCC::is_view_active(trx->read_view)) {
/* At low transaction isolation levels we let each consistent read set its own snapshot */
mutex_enter(&trx_sys->mutex);
trx_sys->mvcc->view_close(trx->read_view, true); //隔离级别小,关闭快照,这样下一条SQL执行时,将获取新快照
mutex_exit(&trx_sys->mutex);
}
}
...
}