目录
purge逻辑分析与调测
一、purge线程
1、什么是purge线程?
2、为什么需要purge?
3、purge哪些内容?
4、什么时候purge?
5、purge内存结构
二、purge主要步骤
1、确认可见性
2、获取需要purge的undo记录
3、分发给purge工作线程清理record
4、清理history list
一、purge线程
1、什么是purge线程?
用于回收已经使用并分配的undo页的线程。
在InnoDB 1.1版本之前purge操作由master线程完成,在之后的版本,purge操作可以独立到单独的线程中进行,减轻master线程的负担,提高CPU使用率以及提升存储引擎的性能。
从MySQL5.7.8版本开始,线程的个数数默认是4,可以在初始化时在my.cnf中通过参数innodb_purge_threads进行设置,最大可以设置为32。
有两类线程:
- 协调线程coordinator thread:srv_purge_coordinator_thread,全局只有1个
- 工作线程worker thread:srv_worker_thread,系统有innodb_purge_threads - 1个
工作线程主要负责具体的回收清理工作。协调线程不仅负责回收清理工作,还负责启动工作线程参与到purge工作中,把purge任务放入队列、唤醒工作线程和truncate undo log。
2、为什么需要purge?
对于更新和删除操作,InnoDB并不是真正的删除原来的记录,而是设置记录的delete mark为1。因此为了解决数据Page和Undo Log膨胀的问题,需要引入purge机制进行回收。
3、purge的内容?
insert一条数据,会产生一条insert undo类型记录,因为insert操作只对事务本身可见,对其他事务不可见(这是事务的隔离性的要求),所以无论这条记录是否insert成功,产生的insert undo类型记录在事务提交或回滚后,可以直接删除。
delete和update操作会产生update undo record,该update undo log可能需要提供MVCC机制,因此不能再事务提交时立即删除。对于delete操作,,仅是将删除标记位delete flag置为1,记录并没有立即被删除,即记录还是存在于B+树中。
提交时会将update undo日志放到history list中,当这些旧版本的update undo record无人访问时,需要等待purge线程进行清理操作。
下面列出了undo log类型,后续在purge工作线程的时候会针对性的讲述不同的处理方式:
- 从表中删除一行记录
TRX_UNDO_DEL_MARK_REC (将主键记入日志),在删除一条记录时,并不是真正的将数据从数据库中删除,只是标记为已删除。 - 向表中插入一行记录
TRX_UNDO_INSERT_REC (仅将主键记入日志)
TRX_UNDO_UPD_DEL_REC (将主键记入日志) 当表中有一条被标记为删除的记录和要插入的数据主键相同时,实际的操作是更新这个被标记为删除的记录。 - 更新表中的一条记录
TRX_UNDO_UPD_EXIST_REC (将主键和被更新了的字段内容记入日志)
TRX_UNDO_DEL_MARK_REC和TRX_UNDO_INSERT_REC,当更新主键字段时,实际执行的过程是删除旧的记录,然后再插入一条新的记录。
purge以页为单位进行清理。
在逻辑上,全局事务系统维护128个回滚段,每个回滚段会维护1024个槽,每个槽里放一类undo log,里面记录的是一条条的undo log record。
每次purge操作的undo log page的数量,默认batch_size为300,可以通过innodb_purge_batch_size参数来设置。
设置的越大,表示每次回收的页也就越多,可供重用的undo page也就越多,就能减少磁盘存储空间与分配的开销。不过该参数设置得太大,则每次需要purge处理更多的undo page,从而导致CPU和磁盘IO过于集中于对undo log的处理,使性能下降。普通用户不建议调整这个参数。
其他的参数:
innodb_max_purge_lag 参数:当InnoDB存储引擎的压力非常大时,并不能高效地进行purge操作。那么history list(undo log page数量)的长度会变得越来越长。innodb_max_purge_lag 就是控制history list的长度,若长度大于该值,就会延缓DML的操作。该值默认为0,表示不做任何限制。【不建议修改这个参数值!! 】
innodb_max_purge_lag_delay 参数:表示当上面innodb_max_purge_lag的delay超时时间太大,超过这个参数时,将delay设置为该参数值,防止purge线程操作缓慢导致其他SQL线程长期处于等待状态。默认为0,一般不用修改。
4、purge内存结构
purge操作中使用一个全局变量purge_sys来维护管理清理操作,它的类型为trx_purge_t:
成员变量 | 解释 |
sess_t* sess | 运行清理查询的系统会话 |
trx_t* trx | 运行清理查询的系统事务 |
rw_lock_t latch | 保护清理视图的锁 |
os_event_t event | 状态信号事件 |
ulint n_stop | 跟踪数字停止的计数器 |
volatile bool running | purge线程是否运行 |
volatile purge_state_t state | 清理线程状态 |
que_t* query | 执行purge操作的SQL |
ReadView view | 当前事务系统中最旧的readview,大于这个readview的undo log不能被purge |
bool view_active | 视图是否处于活跃状态 |
volatile ulint n_submitted | 提交到任务队列的任务总数 |
volatile ulint n_completed | 已完成的任务总数 |
purge_iter_t iter | 当前正在做purge的回滚段 |
purge_iter_t limit | 限制history list的truncate |
ibool next_stored | 是否存储下一条记录的信息 |
trx_rseg_t* rseg | 下一个purge的回滚段 |
ulint page_no | 下一个purge的回滚记录的页码 |
ulint offset | 下一个purge的回滚记录的页面偏移量 |
ulint hdr_page_no | 下一个purge的undo log segment的header page number |
ulint hdr_offset | 下一个purge的undo log segment的header page offset |
TrxUndoRsegsIterator* rseg_iter | 获取下一个要处理回滚段的迭代器 |
purge_pq_t* purge_queue | purge队列,包含所有待purge的回滚段 |
PQMutex pq_mutex | 保护purge_queue的互斥锁 |
undo::Truncate undo_trunc | 跟踪标记位truncate的undo表空间 |
purge的状态
参数名称 | 含义 |
PURGE_STATE_INIT | 初始化 |
PURGE_STATE_RUN | 运行 |
PURGE_STATE_STOP | 停止 |
PURGE_STATE_EXIT | 退出 |
PURGE_STATE_DISABLED | 从未开始(启动)过 |
trx_purge_rec_t清除记录所需的信息
参数名称 | 含义 |
trx_undo_rec_t* undo_rec | 要清除的记录 |
roll_ptr_t roll_ptr | undo记录的文件指针 |
TrxUndoRsegsIterator
参数名称 | 含义 |
trx_purge_t* m_purge_sys | 清除系统指针 |
TrxUndoRsegs m_trx_undo_rsegs | 当前要处理的元素 |
TrxUndoRsegs::iterator m_iter | 在m_trx_undo_rseg中跟踪当前元素 |
static const TrxUndoRsegs NullElement | 哨兵值 |
5、什么时候purge?
purge通过read view来确定清理的范围,保存在purge_sys->view中。如果系统有活跃read view,就选取最老的read view保存在purge_sys->view中
如果不存在就给当前时刻trx_sys的状态打个快照,保存在purge_sys->view,可以被purge的undo log其trx_no一定是小于系统中所有已提交事务的trx->no。
在事务commit时,会把产生的trx->no加入到trx_sys->serialisation_list链表,这个链表是按照trx->no升序次序排列,也就是维护了trx commit顺序。
为了节省存储空间,InnoDB存储引擎的undo log设计是这样的:一个页上允许多个事务的undo log存在。
history list表示按照事务提交的顺序将undo log进行组织。先提交的事务总是在尾端。undo page存放了undo log,由于可以重用,一个undo page中可能存放了多个不同事务的undo log。
举例:
首先从history list中找到第一个需要清理的记录,这里为trx1,,清理之后InnoDB存储引擎会在trx1的undo log所在的页中继续寻找是否存在可以被清理的记录,这里会找到trx3,接着找到trx5,但是发现trx5被其他事务所引用而不能清理,故再次去history list中查找,发现这时最尾端的记录为trx2,接着找到trx2所在的页,然后依次再把事务trx6、trx4的记录进行清理,由于undo page2所在的页都被清理了,因此该undo page可以被重用。
InnoDB存储引擎这种先从history list中扎到undo log,然后再从undo page中找到undo log的设计模式是为了避免大量的随机读取操作,从而提高purge的效率。
主要工作有:清理内存中history list无人访问的update undo log;更改history_len;页内标记删除的操作也需要从物理上清理掉。
二、purge主要步骤
1、确认可见性
首先,通过调用trx-sys->mvcc->clone_oldest_view函数,复制一个最老的活跃视图,并将purge_sys->view_active置为true,表示此视图处于激活状态,在此活跃视图之前提交的事务所产生的undo log都是可以清理的。
2、获取需要purge的undo记录
获取undo log记录,调用的函数为trx_purge_attach_undo_recs:
(1)、循环从链表中取出线程purge_sys->query->thrs,并且将每个线程中的node->done置为false。保证节点数不能少于线程数。
(2)、循环将undo record添加到每个purge节点向量中。获取并解析undo record。从history list中获取下一条undo record以进行清除,调用的函数为trx_purge_fetch_next_rec:
a、选择下一个回滚日志来purge并更新purge_sys中的信息,调用的函数为trx_purge_choose_next_log,这个函数内部调用trx_purge_read_undo_rec函数,来具体更新purge_sys的一些成员值;
b、根据purge_sys中的回滚段的id、page_no和offset,构造指向的undo record的回滚指针roll_ptr,调用的函数为trx_undo_build_roll_ptr;
c、获取下一条待purge的undo record,并以此更新purge_sys中的部分成员信息,调用的函数为trx_purge_getnext_rec。
3、分发给purge工作线程清理record
获取完将要purge的undo记录后,需要将undo record分发给purge工作线程处理。调用的函数为que_run_threads:
分发任务后,各个工作线程(包括协调线程)开始进行purge操作,调用的函数为row_purge_step -> row_purge -> row_purge_record_func:
主要包括两种:一种是记录直接被标记删除了,这时候需要物理清理所有的聚集索引和二级索引记录row_purge_record_func;另一种是聚集索引更新了,但二级索引上的记录顺序可能发生变化,而二级索引的更新总是标记删除 + 插入,因此需要根据回滚段记录去检查二级索引记录序是否发生变化,并执行清理操作row_purge_upd_exist_or_extern。
4、清理history list
在事务提交之后,update undo放到history list,为了将这些文件空间回收重用,需要对其进行truncate操作;默认每处理128轮Purge循环后,Purge协调线程需要执行一次purge history List操作。
调用的函数为trx_purge_truncate --> trx_purge_truncate_history
从回滚段的HISTORY 文件链表上开始遍历释放Undo log segment,由于history 链表是按照trx no有序的,因此遍历truncate直到完全清除,或者遇到一个还未purge的undo log(trx no比当前purge到的位置更大)时才停止。
三、调测结果
1、调测purge_sys->purge_queue队列中的元素:
附:
gdb调试STL容器(string、vector、list、queue、priority_queue、stack、set、multiset 、map、multimap等)中元素的方法:
将stl-views-1.0.3.gdb文件放到gdb调试的当前文件夹,运行gdb后,使用命令source stl-views-1.0.3.gdb包含进去,即可查看容器中的值。
查看使用只需要将ppqueue代替print命令即可。
可以看到优先级队列中的事务中各元素内容,以及size、和capacity。
2、清理内存中history list无人访问的update undo log
清理内存中history list中无人访问的update undo log调用的函数为trx_purge_truncate_history,由协调线程负责,协调线程的编号为18:
history list len很少会变成0,此处为0是因为在调用trx_purge_add_update_undo_to_history函数过程中赋值的结果,在客户端查询show engine innodb status;时还未显示结果。
3、更改history_len
验证了history list len由全局事务系统变量trx_sys->rseg_history_len保存。
可在客户端中使用show engine innodb status\G;命令查看History list length,在gdb调试中可眼查看trx_sys->rseg_history_len的值: