InnoDB---可重复读隔离级别的底层实现原理

可重复读的实现

    Repeatable Read(可重复读):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录(读已经提交的,其实是读早于本事务开始且已经提交的),但是不能看到其他事务对已有记录的更新(即晚于本事务开始的),并且,该事务不要求与其他事务是“可串行化”的。

    这句话的核心,是“但是不能看到其他事务对已有记录的更新”,那么RR隔离级别是怎么保证这一点的呢?

    使用MVCC(多版本并发控制)。InnoDB为每行记录添加了一个版本号(系统版本号),每当修改数据时,版本号加一。
在读取事务开始时,系统会给事务一个当前版本号,事务会读取版本号<=当前版本号的数据,这时就算另一个事务插入一个数据,并立马提交,新插入这条数据的版本号会比读取事务的版本号高,因此读取事务读的数据还是不会变。

    如果数据库并发控制引擎是单纯的封锁协议机制,则应该在读取数据的时候,判断数据项是不是其他事务更新过的。可是InnoDB没有这么做,而是通过如下方式,在RR隔离级别下为事务设置了一个“一致性读视图(即快照)”,之后读取数据,就是根据这个快照来获取,这样,就不能看到他晚于本事务的事务对已有记录的更新(更新生成新版本,必然不在旧的快照所限定的范围内)。

static my_bool snapshot_handlerton(THD *thd, plugin_ref plugin, void *arg)

{

    handlerton *hton= plugin_data<handlerton*>(plugin);

    if (hton->state == SHOW_OPTION_YES && hton->start_consistent_snapshot) //隔离级别是RRstart_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()函数在可重复读隔离级别下创建一个快照,其他隔离级别则不创建快照。


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 only be used with"

                    " 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);

        }

    }

...

}

     从上面的分析可以看出, InnoDB 的可重复读的实现,利用了实现 MVCC 技术的快照技术。这是 MVCC 和基于封锁技术这两个并非控制技术的结合之处。

猜你喜欢

转载自blog.csdn.net/wj420923/article/details/80251379