共计 16138 个字符,预计需要花费 41 分钟才能阅读完成。
简介: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… 本文为阿里云原创内容,未经容许不得转载。