网站为什么需要空间,上海建筑设计院有哪些,网站的结构与布局优化,东莞网站设计讯息undo log的组织形式
此部分是关于Undo log的组织形式的一个介绍#xff1b;主要分为两部分来对undo log的组织形式进行介绍#xff1a;文件结构和内存结构。在介绍这两部分时#xff0c;先从局部出发#xff0c;最后再给出各个部分的联系。
1. 文件结构
首先#xff0c…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_SIZEhistory list包含的page数。History List Base Node参数名为TRX_RSEG_HISTORYhistory list的Base Node。Rollback Segment FSEG Entry参数名为TRX_RSEG_FSEG_HEADERfile 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_INSERTTRX_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_NODEundo page list节点可以把同一个事务所用到的所有undo page双向串联起来。
1.4 Undo Segment Header State参数名为TRX_UNDO_STATEundo segment的状态TRX_UNDO_ACTIVE等Last Log Offset参数名为TRX_UNDO_LAST_LOG当前page最后一个undo log header的位置。Undo Segment FSEG Entry参数名为TRX_UNDO_FSEG_HEADERsegment对应的inode的space_idpage_nooffset等Undo Segment Page List Base Node参数名为TRX_UNDO_PAGE_LISTundo 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 notable id等而undo recod中具体存储的内容我们等到《undo log的分配与记录》中去介绍。
最后我们通过下面这幅图来了解undo log在文件组织上的一个总览。 2. 主要内存数据结构
为了方便管理和记录undo log在内存中有如下关键结构体对象
undo::Tablespaceundo tablespace内存结构体维护undo tablespace相关信息管理此tablespace中相关回滚段。trx_rseg_t回滚段的内存结构体用于维护回滚段相关信息。trx_undo_tundo log内存结构体用于维护undo log type等信息便于对undo page进行维护和管理。purge_pq_tpurge queue对已经提交事务的undo log进行维护和回收。trx_t事务的内存结构体对事务的信息进行管理和维护。
我们重点对trx_rseg_t结构体的内容进行介绍。
trx_rseg_t主要数据成员
last_page_nohistory 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_001undo_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 recordundo 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)同上TypeExtern FlagComp 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_part1也就是说此字段为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_nolast_offsetlast_trx_nolast_del_marks从文件中读取上来。然后通过trx_undo_lists_init()把rseg的四个链表insert_undo_listinsert_undo_cachedupdate_undo_listupdate_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()来判断可见性的具体如下
假设一个事务为Ttrx_id为记录R中的DATA_TRX_ID
trx_id read_view::m_low_limit_idT 不可见 Rtrx_id read_view::m_up_limit_idT 可见 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.23s source codeMySQL 8.0 Reference ManualMySQL Server Team Blog庖丁解InnoDB之Undo LOG数据库故障恢复机制的前世今生浅析数据库并发控制机制Jeremy Coles github A little fun with InnoDB multi-versioningThe basics of the InnoDB undo logging and history systemMySQL · 引擎特性 · InnoDB undo log 漫游InnoDBundo log1InnoDBundo log2原文链接
本文为阿里云原创内容未经允许不得转载。