InnoDB多版本控制 - MVCC

MVCC保证并发时数据的正确性,写操作永远是“单线程”或串行化的,这是解决并发问题的前提。所有并发解决方案都只是在缩短这个串行化过程。

相关概念

Redolog

保证已经commit的数据不会丢失。

Undo log

保证rollback时能找回修改前的数据。

隔离性

多个事务可同时对同一行数据执行修改,但相互不影响。

一致性

在任何时刻,保证不会读到中间状态的数据。

double write buffer保证数据写入的可靠性。

InnoDB为每条记录添加三个字段:

6字节的DB_ROW_ID、6字节DB_TRX_ID、7字节DB_ROLL_PTR。

DB_TRX_ID记录最近一次事务ID。DB_ROLL_PTR中的指针,指向回滚段中行记录对应的Undo log。

删除操作在InnoDB内部被视为更新,行中的一个特殊位会被标记为删除(delete_flag=true)。

Undo Log

Undo Log分为Insert undo logs和Update undo logs,一条数据的Insert undo logs在数据插入和回滚后就被删除。

Update undo logs见证一条数据的更新操作,一次更新产生一条Undo log,Undo Log记录着恢复行数据所需的必要信息,

可在需要时将行数据还原到某个时刻。

一条行数据的Undo log记录以链表形式存在,链表按照事务提交顺序排序,存放在回滚段中。

Update undo logs会被删除,删除取决于是否还需用来还原行数据。

如果,链表太长则有可能是长事务,或者持续的小批量插入和删除操作。

前者需从业务角度合理规化,后者可通过调整innodb_max_purge_lag变量限制新行操作,将更多资源分配给日志清除线程 。

事物间的undo log以最小堆组织,以便删除操作从最老的undo log入手。

注:InnoDB中删除操作被识为更新,只不过会多出一个删除标识,对于二级索引则没有更新操作,取而代之是先删除后插入。

Redo Log

InnoDB引擎遵循ACID标准,在事务提交前刷新事务的Redo Log。

InnoDB将多个事务的Redo Log组合在一起,一次性写入日志文件,提高吞吐量。

一致性读

InnoDB在MVCC中基于时间点获取数据快照,为事务提供数据可见性,基于时间点前提交的事务能被其他事务看到,基于时间点后提交的事务,只能被当前事务看到。

Read view

事务的隔离级别为Repeatable read时,事务需要一致性读时会创建Read view,在读结束的时候释放。

Read view创建后会将全局活跃的读写事务id拷贝一份,从中选取最大值和最小值,确定当前事务可见的范围,获取可见事务对应的数据,作为当前事务的快照。

注:后续新加入的事务,不会包含在拷贝中。

多版本控制

InnoDB利用Read view决定当前事务能看到的数据。

Read view从全局活跃的读写事务id列表,拷贝活跃的读写事务,其中最小值定为up_limit_id,最大值定为low_limit_id。

InnoDB事务id由生成器控制,id就能确定事务间的前后顺序。

InnoDB为当前事务搜寻可见数据时,将搜索到的记录的trx_id字段与up_limit_id比较;

trx_id < up_limit_id:

此记录在read view之前就存在,可被当前事务看到。

trx_id > low_limit_id:

此记录在read view之后生成,不能被当前事务所见。

up_limit_id < trx_id < low_limit_id:

先确定记录的trx_id是否包含在活跃的读写事务列表中,如果不存在,说明事务已提交,可以被当前事务看到。

如果存在,说明ReadView出生时事务还未提交,不应被当前事物看到。要通过Undo Log链找到一个可见的版本(依然使用此规则),如果找到,会看一下记录的delete_flag是否为true,如果为true说明记录已被删除,否则返回数据。

MVCC与二级索引

InnoDB多版本并发控制对二级索引的处理有别于聚簇索引

聚集索引中的记录可以就地更新,且隐藏的列能帮助重建早期记录。

二级索引记录不包含隐藏的列,也不会就地更新。

更新二级索引列时,旧的二级索引记录将被标记删除,以新增方式代替更新,被标记为删除的记录最终会被清除。

二级索引记录被标记为删除或被更新时,InnoDB会查找聚簇索引中的记录。

在聚簇索引中,检查DB_TRX_ID记录,如果,在开启读取事务后记录被修改,则从undo logs中查找正确的记录版本。

如果,二级索引记录被标记为删除或被更新,则不使用覆盖索引技术。

InnoDB会在聚集索引中查找记录,而不从索引结构中返回值。

Redo Log Archiving

在执行备份操作时,复制Redo Log的程序有时可能无法跟上Redo Log的生成速度,导致这些记录被覆盖而丢失Redo Log记录。

这种问题通常发生在备份操作期间MySQL有大量的操作发生,存储Redo Log的设备运行速度往往比备份设备快。

MySQL 8.0.17中引入Redo Log Archiving特性解决此问题,除了Redo Log文件外,还将Redo Log记录按顺序写入归档文件。

备份程序可根据需要从存档文件复制Redo Log记录,避免潜在的数据丢失。

启用Redo Log Archiving需为innodb_redo_log_archive_dirs系统变量设置一个值 。

该值为“Redo Log Archiving目录”的分隔符。如:

mysql> SET GLOBAL innodb_redo_log_archive_dirs='label1:directory_path1[;label2:directory_path2;…]';

须先配置innodb_redo_log_archive_dirs变量,才能激活Redo Log Archiving。默认值为NULL。