简介: undo log是InnoDB事务个性的重要组成部分。当对记录做增删改操作就会产生undo记录,undo记录会记录到独自的表空间中。 本文将从代码层面对undo log进行一个简略的介绍;次要从上面四个方面来介绍undo log:undo log组织模式与调配与记录,以及undo log的利用及其清理。从这四个方面登程,咱们就能够根本理解undo log的整个生命周期。本文基于MySQL Community 8.0.23 Version  undo log的组织模式此局部是对于Undo log的组织模式的一个介绍;次要分为两局部来对undo log的组织模式进行介绍:文件构造和内存构造。在介绍这两局部时,先从部分登程,最初再给出各个局部的分割。1. 文件构造首先,在MySQL5.6之前所有的undo log全副存储在零碎表空间中(ibdata1);然而从5.6开始也能够应用独立表空间来存储undo log。以后版本InnoDB默认有两个undo tablespace,也能够应用CREATE UNDO TABLESPACE语句动静增加,最大128个;每个undo tablespace至少能够有TRX_SYS_N_RSEGS(128)个回滚段。1.1 Rollback Segment(回滚段)InnoDB在undo tablespace中应用回滚段来组织undo log。同时为了保障事务的并发操作,在写undo log时不产生抵触,InnoDB应用 回滚段 来保护undo log的并发写入和长久化;而每个回滚段 又有多个undo log slot。通常通过Rollback Segment Header来治理回滚段,Rollback Segment Header通常在回滚段的第一个页,具体构造如下:

  Max Size:参数名为 TRX_RSEG_MAX_SIZE,回滚段能够有用的最大page数。History Size:参数名为TRX_RSEG_HISTORY_SIZE,history list蕴含的page数。History List Base Node:参数名为TRX_RSEG_HISTORY,history list的Base Node。Rollback Segment FSEG Entry:参数名为TRX_RSEG_FSEG_HEADER,file segment的寄存地位。Undo Slots Dictionary:参数名为TRX_RSEG_UNDO_SLOTS,寄存沉闷事务的undo header page no。Rollback Segment Header外面最重要的两局部就是history list与undo slot directory。其中history list把所有曾经提交但还没有被purge事务的undo log串联起来,purge线程能够通过此list对没有事务应用的undo log进行purge。每个事务在须要记录undo log时都会申请一个或两个slot(insert/update离开),同时把事务的第一个undo page放入对应slot中;所以实践上InnoDB容许的最大事务并发数为128(undo tablespace) 128(Rollback Segment) 1024(TRX_RSEG_UNDO_SLOTS)。上面咱们进一步介绍undo log在磁盘上如何记录。1.2 UndoPage要想晓得undo log如何记录,咱们先要搞清楚一个undo page具体内容,undo page个别分两种状况:header page和normal page 。header page除了normal page所蕴含的信息,还蕴含一些undo segment信息,前面会对undo segment进行具体介绍。咱们上面先介绍一下undo header page的具体散布。

  undo header page是事务须要写undo log时申请的第一个undo page;一个undo header page他同一时刻只隶属于同一个沉闷事务,然而一个undo header page下面的内容可能蕴含多个曾经提交的事务和一个沉闷事务。undo normal page是当沉闷事务产生的undo record超过undo header page容量后,独自再为此事务调配的undo page(参考函数trx_undo_add_page);此page只隶属于一个事务,只蕴含undo page header不蕴含undo segment header。1.3 Undo Page Header每一个undo page都要有header,其中记录了以后undo page的一些状态信息,具体内容如下:

  Undo Page Type:参数名为TRX_UNDO_PAGE_TYPE,应用该page事务的类型,蕴含TRX_UNDO_INSERT,TRX_UNDO_UPDATE两种。Latest Log Record Offset:参数名为TRX_UNDO_PAGE_START,最新事务开始记录undo log起始地位。Free Space Offset:参数名为TRX_UNDO_PAGE_FREE,页内闲暇空间起始地址,在此之后可记录undo log。Undo Page List Node:参数名为TRX_UNDO_PAGE_NODE,undo page list节点,能够把同一个事务所用到的所有undo page双向串联起来。1.4 Undo Segment Header

  State:参数名为TRX_UNDO_STATE,undo segment的状态,TRX_UNDO_ACTIVE等Last Log Offset:参数名为TRX_UNDO_LAST_LOG,以后page最初一个undo log header的地位。Undo Segment FSEG Entry:参数名为TRX_UNDO_FSEG_HEADER,segment对应的inode的(space_id,page_no,offset等)Undo Segment Page List Base Node:参数名为TRX_UNDO_PAGE_LIST,undo page list的Base Node,对于同一个事务下的undo header page和undo normal page形成双向链表。下面只是介绍了一些undo log在文件上的根本构造,上面咱们持续介绍记录undo log时的文件组织。1.5 Undo Log Header当事务开始记录undo log时,先创立一个undo log header,当update/delete事务完结后,undo log header将会被退出到hisotry list中;insert事务的undo log会被立刻开释。

  Transaction ID:参数名为TRX_UNDO_TRX_ID,事务id(事务开始的逻辑工夫)Transaction Number:参数名为TRX_UNDO_TRX_NO,事务no(事务完结的逻辑工夫)Delete Marks Flags:参数名为TRX_UNDO_DEL_MARKS,如果波及到删除记录为TRUELog Start Offset:参数名为TRX_UNDO_LOG_START,事务中第一个undo record地址。XID Flag:参数名为TRX_UNDO_XID_EXISTS,用于XID。DDL Transaction Flag:参数名为TRX_UNDO_DICT_TRANS,是否是DDL事务Table ID if DDL Transaction:参数名为TRX_UNDO_TABLE_ID,如果是DDL事务,记录table id。Next Undo Log Offset:参数名为TRX_UNDO_NEXT_LOG,当前页的下一个undo log header地位Prev Undo Log Offset:参数名为TRX_UNDO_PREV_LOG,当前页的上一个undo log header地位History List Node:参数名为TRX_UNDO_HISTORY_NODE,事务完结时放入history list的节点。无关XID的内容临时不介绍。有了undo log header后,咱们就能够记录undo record了。1.6 Undo Record

  Previous Record Offset:存储上一条record的地位。Next Record Offset:存储下一条record的地位。Type + Extern Flag + Compilation Info:存undo record的type等信息。能够从下面图中看出,undo record除了存储这里比拟重要的几个信息,蕴含前后undo record地位,类型,undo no,table id等;而undo recod中具体存储的内容咱们等到《undo log的调配与记录》中去介绍。最初,咱们通过上面这幅图来理解undo log在文件组织上的一个总览。

  2. 次要内存数据结构为了方便管理和记录undo log,在内存中有如下要害构造体对象:undo::Tablespace:undo tablespace内存构造体,保护undo tablespace相干信息,治理此tablespace中相干回滚段。trx_rseg_t:回滚段的内存构造体,用于保护回滚段相干信息。trx_undo_t:undo log内存构造体,用于保护undo log type等信息,便于对undo page进行保护和治理。purge_pq_t:purge queue,对曾经提交事务的undo log进行保护和回收。trx_t:事务的内存构造体,对事务的信息进行治理和保护。咱们重点对trx_rseg_t构造体的内容进行介绍。trx_rseg_t次要数据成员:last_page_no:history list上此rseg最初一个没有被purge的page no。last offset:最初一个未被purge的undo log header 偏移。last trx no:最初一个未被purge的事务no。last_del_marks:最初一个未被purge的日志须要被清理。下面四个数据能够从trx_undo_t中获取,参考trx_purge_add_update_undo_to_history函数。trx_ref_count:被沉闷事务援用的计数器;非0时,此回滚段所在的tablespace不能够被truncate。update_undo_list:所有沉闷的update事务的trx_undo_t对象存储在此链表。update_undo_cached:如果update事务提交时,此事务只应用了一个page并且此page残余空间大于1/4放入此链表;新update事务新申请undo log时优先从此链表调配。insert_undo_list:所有沉闷的insert事务的trx_undo_t对象存储在此链表。insert_undo_cached:如果insert事务提交时,此事务只应用了一个page并且此page残余空间大于1/4放入此链表;新insert事务新申请undo log时优先从此链表调配。上面咱们通过一副关系图来介绍内存中各个要害构造体之间的关系;其中实线代表领有该对象,虚线代表援用该对象。

  undo log的调配与记录咱们通过之前的介绍理解到;undo log在磁盘和内存中是如何组织的;从中理解到,回滚段不管在磁盘和内存中,都是一个十分要害的构造体;InnoDB存储引擎通过回滚段来对undo log进行组织和治理,所以首先咱们须要弄清楚回滚段是如何调配与应用的,之后再论述undo log具体是如何记录的。1. 调配回滚段当开启一个事务的时候,须要事后为事务调配一个回滚段。首先咱们将事务分为两大类:只读事务与读写事务。别离从这两大类事务来探讨如何调配回滚段的。只读事务:当事务波及到对长期表的读写时,咱们须要为其调配一个回滚段对其产生的undo log record进行记录,具体调用链路如下:trx_assign_rseg_temp() -> get_next_temp_rseg() -> (trx_sys->tmp_rsegs)trx_sys->tmp_rsegs 对应的临时文件为ibtmp1,一般来说有128个回滚段。读写事务:当一个事务被断定为读写模式时,会为其调配trx_id以及回滚段,具体调用链路如下trx_assign_rseg_durable() -> get_next_redo_rseg()                                                  |                                                  ->get_next_redo_rseg_from_trx_sys() -> (trx_sys->rsegs)                                                  |                                                  ->get_next_redo_rseg_from_undo_spaces() -> (undo_space->rsegs())当InnoDB没有配置独立undo表空间时,从trx_sys->rsegs为读写事务调配回滚段;否则则从 undo_spaces->rsegs()为其调配回滚段;InnoDB从MySQL 8.0.3开始,独立表空间个数默认值从0改为2。trx_sys->rsegs 对应的文件为ibdata1,默认有128个回滚段。undo_space->rsegs() 对应的文件为undo_001,undo_002...,最多可有128个undo文件,每个文件默认128个回滚段。具体从rsegs中调配时采纳round-robin形式进行调配。2. 应用回滚段当产生数据变更时,咱们须要应用undo log记录下变更前的数据记录;因而须要从回滚段调配中来调配一个undo slot来供事务记录undo。记录undo的入口函数为trx_undo_report_row_operation,其大抵流程如下:判断操作的表是否为长期表;如果是长期表,为其调配长期表回滚段,否则应用一般回滚段。依据事务类型,通过trx_undo_assign_undo为其调配trx_undo_t对象;之后事务产生的undo记录在此对象中。依据事务类型,通过trx_undo_page_report_insert/modify,来记录insert/update事务产生的undo。接着来看一下 trx_undo_assign_undo 函数流程:首先尝试通过trx_undo_reuse_cached() 来获取可用的undo log对象。对于INSERT类型的undo log,咱们从rseg->insert_undo_cached链表上获取undo log对象,并将其从链表上移除;之后通过trx_undo_insert_header_reuse()从新初始化undo page头部信息。对于UPDATE/DELETE类型undo log,从rseg->update_undo_cached链表上获取undo log对象,并将其从链表上移除;而后通过trx_undo_header_create()创立新的undo log header。而后应用trx_undo_header_add_space_for_xid() 作用于上述undo log对象,预留XID存储空间。最初应用trx_undo_mem_init_for_reuse()初始化undo log对象相干信息。如果没有缓存的undo log对象,咱们就须要应用trx_undo_create()从回滚段上调配一个闲暇的undo slot,并调配一个undo page,对其初始化。将曾经调配好的undo log对象放入相干的链表中(rseg->insert_undo_list或rseg->update_undo_list)。最初,如果这个事务时DDL操作,须要将undo_hdr_page(事务记录undo log的第一个page)中的TRX_UNDO_DICT_TRANS置为TRUE.undo header page构造参考之前的《undo log的组织模式》的内容undo log最小的并发单元为undo slot,所以undo log反对最大的并发事务为:undo tablespace 数 回滚段数 undo slot数。3. undo log写入当调配完undo slot,初始化完undo log对象后,咱们就能够记录真正的undo log record;undo log record也分为一下两种,insert undo log record与update undo record。当数据库须要批改某个数据记录时,都会写入一条update undo log record;当插入一条数据记录时,会写入一条insert undo log record。

  对于insert undo log写入的入口函数为trx_undo_page_report_insert()Prev record offset (2):本条record开始的地位。Next record offset (2):下一条record开始的地位。Type (1):标记undo log record的类型,此处个别为 TRX_UNDO_INSERT_REC.Undo Number (1-11):trx->undo_no,事务的第几条undo。Table ID (1-11):汇集索引所对应的table id。Unique Fields:惟一键值对于update undo log写入的入口函数为trx_undo_page_report_modify()Prev record offset (2):同上Next record offset (2):同上Type+Extern Flag+Comp Info (1):Type为undo log rec的类型,此处个别有三种:TRX_UNDO_DEL_MARK_REC: 标记删除操作,未修改任何列值;可能由一般删除操作产生,也有可能由批改汇集索引产生,因为批改汇集索引操作被分拆为删除+插入操作。TRX_UNDO_UPD_DEL_REC: 更新一个曾经被删除的记录;如某个记录被删除后,在很快插入一个雷同的记录;之前的记录若未被purge,就可能重用该记录所在位置。TRX_UNDO_UPD_EXIST_REC: 更新一个未被标记删除的记录,也就是一般更新。 Extern Flag:是否有内部存储列,以提醒purge线程去清理内部存储。Comp Info:更新相干信息,例如更新是否导致索引序发生变化。Undo Number (1-11):同上Table ID (1-11):同上Info Bits (1):是否标记删除REC_INFO_DELETED_FLAG.Data Trx ID (1-11):批改旧记录的事务ID。Data Roll Ptr (1-11):旧记录的回滚指针。Unique Fields:惟一键值Update Get N Fields (1-5): 更新的列数。UPD Old Columns:产生更新时,旧记录的内容。Delete Fileds len (2): 删除的列数。DEL Old Columns:产生删除时,旧记录的内容;产生删除时并不总是记录旧记录,只有ord_part=1也就是说此字段为n_uniq之一的字段,才会记录旧字段。在写入过程中,可能呈现undo page空间有余的状况;当呈现这种状况,咱们须要通过trx_undo_erase_page_end()来革除刚刚写入的区域,而后通过trx_undo_add_page()申请一个新的undo page退出到undo page list,同时将undo->last_page_no指向新的undo page,最初重试写入。实现undo log record的写入后,通过trx_undo_build_roll_ptr()构建新的回滚指针返回;通过回滚指针咱们能够找到相干记录的undo log record,从而构建出旧版本的数据;回滚指针将会记录在汇集索引记录中。undo log的利用咱们通过之前的介绍曾经理解到, undo log的组织形式与调配记录;那么前面咱们持续介绍undo log次要的利用是什么。undo log的利用次要有两方面:事务回滚,解体复原;此性能次要满足了事务的原子性,简略的说就是要么做完,要么不做。因为数据库在任何时候都可能产生宕机;包含停电,软硬件bug等。那数据库就须要保障不论产生任何状况,在重启数据库时都能复原到一个一致性的状态;这个一致性的状态是指此时所有事务要么处于提交,要么处于未开始的状态,不应该有事务处于执行了一半的状态;所以咱们能够通过undo log在数据库重启时把正在提交的事务实现提交,沉闷的事务回滚,这样就保障了事务的原子性,以此来让数据库复原到一个一致性的状态。多版本并发管制(MVCC),此性能次要满足了事务的隔离性,简略的说就是不同沉闷事务的数据相互可能是不可见的。因为如果两个沉闷的事务彼此可见,那么一个事务将会看到另一个事务正在批改的数据,这样会产生数据错乱;所以咱们能够借助undo log记录的历史版本数据,来复原出对于一个事务可见的数据,来满足其读取数据的申请。咱们接下来就具体介绍下面两个性能undo log是如何实现的。1. 解体复原在InnoDB因为某些起因进行运行后;重启InnoDB时,可能存在一个不统一的状态,这个时候咱们就须要把MySQL复原到一个统一的状态来保障数据库的可用性。这个复原过程次要分上面这么几步:把最新的undo log从redo log中复原进去,因为undo log是受redo log爱护的。依据最新的undo log构建出InnoDB解体前的状态。回滚那些还没有提交的事务。通过下面这三步后,InnoDB就能够复原到一个统一的状态,并且对外提供服务。上面咱们具体的来介绍这三局部的具体过程:1.1 undo log的复原因为undo log受到redo log的爱护,所以咱们只须要依据最新的redo log就能够把undo log复原到最新的状态;具体的调用过程如下:recv_recovery_from_checkpoint_start()// 从最新的一个log checkpoint开始读取redo log并利用。     |     -> recv_recovery_begin() // 将redo log读取到log buffer中,并将其parse到redo hash中             |             -> recv_scan_log_recs() // 扫描 log buffer中的redo log,并将redo hash中的redo log利用                     |                     -> recv_apply_hashed_log_recs() // 利用redo log到其对应的page上。                             |                             ->recv_apply_log_rec()->recv_recover_page()->recv_parse_or_apply_log_rec_body() -> MLOG_UNDO_INSERT…通过上述的流程之后,undo log就能够复原到InnoDB解体前的最新的状态;尽管undo log曾经复原到最新的状态,然而InnoDB还没有复原到解体前的最新状态;所以下一步咱们就须要依据最新的undo log把InnoDB解体前的内存构造都复原进去。1.2 构建InnoDB解体前的状态构建InnoDB解体前的状态,次要是复原解体前最新事务零碎的状态;通过该状态咱们能够晓得那些事务曾经提交,那些事务还未提交,以及那些事务还未开始。咱们从后面两章的介绍,回滚段不论在内存中还是在文件中都是组织undo log的重要数据结构;所以咱们首先须要把回滚段的内存构造复原进去,而后依据内存中的回滚段,把沉闷的事务复原进去。其具体过程在函数trx_sys_init_at_db_start()中实现,其大抵步骤如下:通过trx_rsegs_init()扫描文件中的回滚段构造,来把rseg的内存构造复原进去。通过trx_rseg_mem_create()把last_page_no,last_offset,last_trx_no,last_del_marks从文件中读取上来。而后通过trx_undo_lists_init()把rseg的四个链表:insert_undo_list,insert_undo_cached,update_undo_list,update_undo_cached从磁盘上复原进去。在rseg内存构造复原好之后,咱们再通过trx_lists_init_at_db_start()把沉闷的事务从rseg中复原进去。通过trx_resurrect_insert()复原沉闷的插入类型的事务。通过trx_resurrect_update()复原沉闷的更新类型的事务。至此,咱们就曾经把InnoDB解体前的内存和文件状态都曾经复原进去了;其实这个时候InnoDB曾经能够对外提供服务了,(毕竟内存和文件状态都就绪后咱们也就能够放弃一致性了);那么最初一步的事务回滚就能够交给后盾线程来缓缓做事务回滚,不影响主线程对外提供服务了。1.3 事务回滚事务须要回滚次要有两种状况:事务产生异样:如产生在解体复原时;其沉闷事务尽管被复原进去,然而无奈持续,须要将其回滚。事务被显式回滚:如用户关上一个事务,执行完某些操作后须要将其回滚。那么在回滚时,咱们就须要借助undo log中的旧数据来把事务复原到之前的状态;其入口函数为row_undo_step();其操作就是通过undo log来读取旧的数据记录,而后做逆向操作;次要分为上面这么几类:对于标记删除的记录清理删除标记。对于in-place更新,将数据更新为老版本。对于插入操作,删除汇集索引记录和二级索引记录。先通过row_undo_ins_remove_sec_rec()删除二级索引记录。再通过row_undo_ins_remove_clust_rec()删除汇集索引记录。2. 多版本并发管制(MVCC)多版本并发管制简略的说就是以后事务只能看见曾经提交的数据记录,看不到正在批改的数据记录。所以咱们只有弄清楚那些事务对于以后事务是曾经提交的,那些事务对于以后事务是沉闷的。为了实现上述的性能咱们先介绍几个比拟要害的概念:trx::id:事务开始的逻辑工夫,也叫事务ID,在事务开始时通过trx_start_low()调配。trx::no:事务完结的逻辑工夫,在事务完结的时候通过trx_commit_low()调配。trx_sys::rw_trx_ids:以后沉闷的事务ID;事务在开始时ID会被增加至此数据结构中;事务提交时ID会被从此数据结构中删除。在结构一条数据记录时,咱们除了在数据记录中增加用户自主增加的数据列,零碎还会主动调配一些零碎列,具体包含:DATA_TRX_ID:批改过此行数据记录的最新事务ID。DATA_ROLL_PTR:指向这条数据记录的上一个版本的指针,上一版数据在undo log中。通过下面几个数据结构的介绍,咱们大略理解了一些基本概念;然而这对于一个事务来判断那些事务对它是曾经提交的,那些事务对它是沉闷的还是远远不够的;所以咱们接下来介绍MVCC中最重要的一个数据结构:视图,也就是read view。2.1 视图每一个事务在读取数据时都会被调配一个视图,通过视图就能够来判断其余事务对数据记录的可见性。上面咱们来具体介绍一下视图是如何运作的。调配:次要通过trx_assign_read_view()来给一个事务调配视图;在事务的隔离级别是Consistent Snapshot 或 Read Repeatable时,事务开始时会给其调配;其余状况下当事务须要读取数据时将会给其调配一个视图。回收:事务完结时,会通过view_close()对其视图进行回收。几个要害数据结构:m_low_limit_id:高水位,调配时取trx_sys::max_trx_id,也就是取以后还没有被调配的事务ID。m_up_limit_id:低水位,如果m_ids不为空,取其最小值,否则取trx_sys::max_trx_id。m_ids:在此视图初始化时,通过copy_trx_ids()从trx_sys::rw_trx_ids拷贝一份沉闷事务ID(不蕴含以后事务ID)。那么有了下面这些数据咱们就能够判断那些事务对于此视图是沉闷的,那些事务对于此视图是曾经提交的。那些事务对于此视图是沉闷的:trx_id > read_view::m_low_limit_idread_view::m_up_limit_id < trx_id < read_view::m_low_limit_id,并且 trx_id 属于 trx_t::read_view::m_ids如果给定一个trx_id满足下面两个条件其中之一,那么这个事务对于此视图就是沉闷的。那些事务对于此视图是曾经提交的:trx id < read_view::m_up_limit_idread_view::m_up_limit_id < trx id < read_view::m_low_limit_id,并且 trx id 不属于 trx_t::read_view::m_ids如果给定一个trx_id满足下面两个条件其中之一,那么这个事务对于此视图就是曾经提交的。2.2 数据可见性通过下面的介绍,那么一个事务就能够通过此事务的视图来对数据记录判断可见性了。具体是通过ReadView::changes_visible()来判断可见性的,具体如下:假如一个事务为T,trx_id为记录R中的DATA_TRX_ID:trx_id > read_view::m_low_limit_id,T 不可见 Rtrx_id < read_view::m_up_limit_id,T 可见 Rread_view::m_up_limit_id =< trx_id <= read_view::m_low_limit_id时,如果trx_id 属于 trx_t::read_view::m_ids 时,T 不可见 R。否则可见 R如果T对R不可见,就须要R中的DATA_ROLL_PTR来结构出上一个数据页版本,直至记录可见。咱们通过上面一个例子来阐明可见性。

 undo log 的清理咱们通过之前的系列文章曾经理解到undo log在磁盘和内存中是如何组织的;undo log是如何调配的;以及undo log是如何应用的。那么undo log会始终记录上来么?当然不是,有些undo log如果没用的话是会被回收清理的。那么上面这将会介绍那些undo log能够清理,以及undo log是怎么进行清理的。1. 几个要害的数据结构在介绍undo log 清理之前,先介绍几个要害的数据结构;这几个数据结构对于undo log的清理实现是至关重要的。trx_sys->serialisation_list: 外面寄存的是正在提交的事务,依照trx_t::no有序的排列;事务会在开始提交时通过 trx_serialisation_number_get() 增加至该数据结构,事务完结提交时通过trx_erase_lists()将该事务从该数据结构中移除。read_view::m_low_limit_no:领有该read_view的对象,对于trx_t::no小于read_view::m_low_limit_no的undo log都不在须要;该变量的取值时trx_sys->serialisation_list中最早的一个事务的trx_t::no;因为trx_sys->serialisation_list内有序寄存的正在提交的事务,如果一个事务的trx_t::no比该数值还小,那么这个事务肯定曾经提交了。TRX_RSEG_HISTORY与TRX_UNDO_HISTORY_NODE:这两个值咱们之前在《undolog的组织模式》里简略介绍过,这两个值独特将回滚段中的history list组织起来;在事务提交时,如果是update/delete类型的undo log,将其undo log header以头插法的形式通过trx_purge_add_update_undo_to_history()退出到该回滚段的history list中,如果是insert类型的undo log其空间会被当场开释,这是因为insert记录没有旧的版本;因而history list中的undo log header是以trx_t::no降序排列的,这里须要留神一下:history list外面的节点是undo log header。上面咱们通过一幅图来具体阐明下磁盘上history list的构造。

  2. 那些undo log能够清理?对于一个事务来说,早于read_view::m_low_limit_no的undo log都不须要拜访了;那么如果存在一个read view,其read_view::m_low_limit_no比所有read view的m_low_limit_no都要小,那么小于此read_view::m_low_limit_no的undo log就不在被所有沉闷事务所须要了,那么这些undo log就能够清理了。在read_view初始化时,会应用头插法通过view_open()插入到一个全局视图链表(MVCC::m_views)中,在事务完结时通过view_close()会从全局视图链表中将此read view移除;因为是程序插入,所以此链表中最初一个还没有close的视图就可以看做是最老的一个视图;小于此视图的undo log能够被清理,个别将此视图赋值给purge_sys::view。当初咱们曾经能够决定那些undo log是能够被清理的,那么下一步咱们还须要找到具体那些undo log能够清理。在事务提交时,此事务对应的回滚段会通过trx_serialisation_number_get()退出到purge_sys::purge_queue中。purge_sys::purge_queue是一个以回滚段中第一个提交事务的trx_t::no为key的优先级队列。如此一来,从purge_sys::purge_queue取出的回滚段中肯定蕴含最老提交的事务,将此事务的trx_t::no与purge_sys::view比照,即可判断出此事务相干的undo log是否能够被清理。purge_sys::purge_queue的详细信息如下图:

  3. undo log怎么清理?解决了那些undo log能够清理的问题后,上面接着持续看undo log怎么进行清理的问题。当放入history list的undo log且不会再被拜访时,须要进行清理操作,另外数据页下面的标记删除的操作也须要清理掉,有一些purge线程负责这些操作,去入口函数为srv_do_purge() -> trx_purge(),其大抵流程如下:通过trx_sys->mvcc->clone_oldest_view()获取最老的视图复制给purge_sys::view,不便之后真正purge undo log时判断其是否不会再被拜访到了。  通过trx_purge_attach_undo_recs()获取须要被purge的undo log,其大抵流程如下:  通过trx_purge_fetch_next_rec()循环获取能够被purge的undo log,默认一次最多获取300个undo log record,能够通过innodb_purge_batch_size来调整。  循环获取能够被purge的undo log record大抵流程如下:  从purge_sys::purge_queue取出第一个回滚段,从其history list上读取最老还未被purge的事务的undo log header。从此undo log header顺次读取undo log record。读取结束后,从新统计此回滚段最老还未被purge的事务的位点,而后从新放入purge_sys::purge_queue;最初回到第一步。 将这些undo log分发给purge工作线程,purge工作线程的入口函数为row_purge_step()->row_purge()->row_purge_record()。  这里purge undo log record时次要分为两种状况:清理TRX_UNDO_DEL_MARK_REC记录或者清理TRX_UNDO_UPD_EXIST_REC记录清理TRX_UNDO_DEL_MARK_REC类型的记录,须要通过row_purge_del_mark()将所有的汇集索引与二级索引记录都革除掉。清理TRX_UNDO_UPD_EXIST_REC类型的记录,须要通过row_purge_upd_exist_or_extern()将旧的二级索引清理掉。  通过trx_purge_truncate()来对history list进行清理,其大抵流程如下:  遍历所有回滚段,并通过trx_purge_truncate_rseg_history()对回滚段中的history list进行清理,其大抵流程如下:  将history list最初一个事务的undo log header读取进去。判断此undo log是否曾经被purge,如果曾经被purge则持续;如果没有被purge则退出。将此事务所有的undo log开释,并从history list上删除;会到第一步。 在这之后,如果发现某些undo tablespace空间占用过大,被标记须要通过trx_purge_truncate_marked_undo()进行对其truncate,其大抵流程如下:创立一个undo_trunc.log的标记文件,来表明以后undo tablespace正在进行truncate;这是为了保障在truncate两头产生重启时能够顺利重建此undo tablespace。通过trx_undo_truncate_tablespace()接口来对其文件做真正的truncate。删除undo_trunc.log标记文件,表明undo tablespace的truncate曾经实现。留神:当一个undo tablespace被标记为须要truncate时,不会再有事务从此undo tablespace调配回滚段,而且进行truncate时必须保障该undo tablespace上所有的undo log都曾经被purge。4. 最初通过下面的介绍,咱们晓得了undo log那些记录能够被清理以及是怎么清理的,然而清理undo log过程中还有很多繁冗的细节;比方清理索引时波及到对B树的操作,以及旧版本数据的构建,XA事务,BLOB等等;这类内容临时略过前面有机会持续介绍。参考内容MySQL 8.0.23's source codeMySQL 8.0 Reference ManualMySQL Server Team Blog庖丁解InnoDB之Undo LOG数据库故障复原机制的前世今生浅析数据库并发管制机制Jeremy Cole's github A little fun with InnoDB multi-versioningThe basics of the InnoDB undo logging and history systemMySQL · 引擎个性 · InnoDB undo log 漫游InnoDB:undo log(1)InnoDB:undo log(2) 原文链接:http://click.aliyun.com/m/100...本文为阿里云原创内容,未经容许不得转载。