[Mysql]Innodb的快照读实现

前言 有一次面试,面试官问我:mysql事务隔离级别有哪些? 我:balabala…… 面试官问:那可重复读是怎么实现的? 我:emm。。第一次读会有快照。。 面试官:嗯。。 我:。。。 然后呢,然后就不知道啦。 事实上,Innodb的RC和RR隔离级别下,读有**快照读(snapshot read)和当前读(current read)**之分,当前读就是SELECT ... LOCK IN SHARE MODE和SELECT ... FOR UPDATE,快照读就是普通的SELECT操作。 快照读的实现,利用了undo log和read view。 快照读不是在读的时候生成快照,而是在写的时候保留了快照。 快照读实现了Multi-Version Concurrent Control(多版本并发控制),简称MVCC,指对于同一个记录,不同的事务会有不同的版本,不同版本互不影响,最后事务提交时根据版本先后确定能否提交。 但是,Innodb的读写事务会加排他锁,不同版本其实是串行的,所以首先要指出的是,Innodb事务快照读不是严格的MVCC实现。 实现 隐藏字段 Innodb每一行都有三个隐藏字段,分别是DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR。 DB_ROW_ID:如果表没有设置主键,用来作为记录的主键,因为Innodb使用聚簇索引的方式存储,记录必须有主键。 DB_TRX_ID:记录修改这行记录事务的id。 DB_ROLL_PTR:指向这行记录的前一个版本。 undo log 多版本其实是用undo log来实现的,undo log听起来是做回滚使用的,没错,但是事务提交后undo log可不会立刻清除,它作为历史版本存在着。只有当前没有事务依赖在这行记录上时,mysql的清理线程才会清理掉无用的undo log。 insert操作的undo log在事务回滚或提交后就会删除,因为只有回滚会用到。 update、delete的undo log需要保留(可见delete只是逻辑删除,其实也是个update操作,逻辑删除后由后台线程清理)。 下面假设有一条记录如下,column_1、column_2初始值为1和2。 假设这时有个三个事务ABC,A,C事务开始,对这条记录的查询结果如下: column_1 column_2 1 2 接着A事务执行更新column_1为11; 具体过程是:给记录加X锁,复制记录为undo_log_1,然后再将记录的column_1改为11,DB_TRX_ID为A,DB_ROLL_PTR指向前一个版本。 虽然数据行和undo log画的一样,但实际undo log有自己的数据结构。 A事务提交,释放X锁。 接着B开启事务,执行更新column_2为22; 具体过程是:给记录加X锁,复制A事务更新完的记录为undo_log_2,然后再将记录的column_2改为22,DB_TRX_ID为B,DB_ROLL_PTR指向前一个版本。 然后B事务提交。 注意!!如果A事务没有提交,X锁是不会释放的,那么B事务对这行记录执行update为了获取X锁会阻塞住的,而MVCC标准各个版本应该是不会相互影响的,所以说Innodb事务快照读不是严格的MVCC实现。 那么问题来了,C事务这时执行第二次查询,查询结果会是什么呢。 read view 光有多版本还不够,需要一个机制对undo log进行可见性判断,决定当前事务读到的是哪个版本,这个机制就是通过read view完成, read view是对当前系统中活跃的所有事务列表的封装,注意是所有事务,而不是作用于目标行的事务。 read view最早的事务id记为up_limit_id,最迟的事务id记为low_limit_id(low_limit_id = 未开启的事务id = 当前最大事务id+1),活跃事务id列表记为descriptors。...

December 11, 2019