博客
关于我
浅析InnoDB purge线程
阅读量:102 次
发布时间:2019-02-26

本文共 6829 字,大约阅读时间需要 22 分钟。

作者:八怪(高鹏) 中亦科技数据库专家

水平有限,如有错误请谅解。源码版本8.0.21。


在处理一个故障的时候怀疑大量的删除数据导致了查询比较慢,但是自己对purge线程的工作流程一直不太清楚,本文不做深入解析,只做工作流程解析,待着如下问题进行:

  • del flag记录是否能够及时清理

  • 为什么History list length持续不为0,是否代表del flag记录没有清理

  • purge线程触发的规则是什么

一、purge线程综述

一般来讲我们理解的purge线程可以做如下的工作:

  • 清理del flag标签的记录

  • 清理undo的历史版本

  • 如果需要进行undo tablespace截断。

其包含一个协调线程和多个工作线程由如下参数设置:

innodb_purge_threads=4

这代表1个协调线程和3个工作线程。协调线程也会充当一个工作线程角色。

二、协调线程循环检测变化

如下调入:srv_purge_coordinator_thread ->srv_purge_coordinator_suspend判断如下:(rseg_history_len <= trx_sys->rseg_history_len) { //如果当前history_len大于等于上一次循环的的history_len      ret =os_event_wait_time_low(slot->event, SRV_PURGE_MAX_TIMEOUT, sig_count); //等待10毫秒后进行处理或者等待被唤醒

唤醒的条件是有事务提交或者回滚

    /* Tell server some activity has happened, since the trx    does changes something. Background utility threads like    master thread, purge thread or page_cleaner thread might    have some work to do. */    srv_active_wake_master_thread();

但是需要注意的是如果长期没有新的事务进行提交,那么可能进入永久堵塞状态而不是每10毫秒醒来,直到唤醒

if (ret == OS_SYNC_TIME_EXCEEDED) { //如果是等待超时      if (rseg_history_len == trx_sys->rseg_history_len &&          trx_sys->rseg_history_len < 5000) { //如果上次的history_len和本次history_len相同且小于5000那么需要等待唤醒        stop = true; //设置为true,进行无限期等待,直到唤醒      }

三、克隆最老的read view

这一步没什么好说的,因为清理undo需要根据当前最老的read view来清理,否则可能清理到正在读取需要的undo。

如下调入:srv_purge_coordinator_thread ->srv_do_purge  ->trx_purge操作如下:trx_sys->mvcc->clone_oldest_view(&purge_sys->view); //克隆老的 read view srv_do_purge

四、从可能需要清理的purge_queue中取出undo segment(简单理解为事务)

调入如下:srv_purge_coordinator_thread ->srv_do_purge  ->trx_purge   ->trx_purge_attach_undo_recs    ->trx_purge_fetch_next_rec     ->TrxUndoRsegsIterator::set_next操作如下:const page_size_t &page_size = purge_sys->rseg_iter->set_next();

注意这里是一个迭代器,迭代的就是purge_sys->purge_queue,这是std::priority_queue实现的优先队列。具体迭代的代码如下:

while (!m_purge_sys->purge_queue->empty()) { //如果有事务需要清理      if (m_trx_undo_rsegs.get_trx_no() == UINT64_UNDEFINED) {        m_trx_undo_rsegs = purge_sys->purge_queue->top();      } else if (purge_sys->purge_queue->top().get_trx_no() ==                 m_trx_undo_rsegs.get_trx_no()) {        m_trx_undo_rsegs.append(purge_sys->purge_queue->top()); //弹出一个      } else {        break;      }

而事务进入purge_queue是在事务commit的时候调用trx_serialisation_number_get

purge_sys->purge_queue->push(elem);

因此到这里我们知道事务提交的时候可能会唤醒purge协调线程进行工作,并且会加入可能需要purge的事务队列purge_queue中。

五、判断是否符合清理规则

调入如下:srv_purge_coordinator_thread ->srv_do_purge  ->trx_purge   ->trx_purge_attach_undo_recs    ->trx_purge_fetch_next_rec判断如下:  if (purge_sys->iter.trx_no >= purge_sys->view.low_limit_no()) {    return (nullptr);  }

这里就是判断是否需要清理事务的trx no是否大于了oldest read view的low limit no,如果不满足则返回为nullptr,如果符合那么返回需要清理的page数量,并且指向下一个需要清理的undo segment。

六、每次清理默认为300个page

这个值由参数innodb_purge_batch_size进行控制,默认为300

调入如下:srv_purge_coordinator_thread ->srv_do_purge  ->trx_purge   ->trx_purge_attach_undo_recs生效如下:for (ulint i = 0; n_pages_handled < batch_size; ++i)

清理流程会一致持续到没有page需要清理为止

调入如下:srv_purge_coordinator_thread ->srv_do_purge判断如下: (!srv_purge_should_exit(n_pages_purged) && n_pages_purged > 0 &&           purge_sys->state == PURGE_STATE_RUN); //清理完成后n_pages_purged > 0 将不会满足return (rseg_history_len); //返回 rseg_history_len

七、工作线程处理

分发给工作线程后进入如下调用,进行del flag的清理,没有仔细的看这部分,调用比较复杂。但是可以肯定是其构造row_purge_parse_undo_rec)和删除过程可能需要大量的循环和数据定位(btr_cur_search_to_nth_level)操作。

srv_worker_thread ->srv_task_execute  ->que_run_threads   ->que_run_threads_low    ->que_thr_step     ->row_purge_step      ->row_purge       ->row_purge_record_func

八、默认每128次batch undo清理会进行undo history清理

这个和参数innodb_purge_rseg_truncate_frequency的设置有关,默认为128,如果满负荷计算为 :

  • 300(undo log pages)*128(truncate frequency ) = 38,400

38400个undo log pages处理完成后会进行一次undo history清理。

根据参数赋值set_rseg_truncate_frequency(        static_cast
(srv_purge_rseg_truncate_frequency));参数判断    ulint rseg_truncate_frequency = ut_min(        static_cast
(srv_purge_rseg_truncate_frequency), undo_trunc_freq); //128    n_pages_purged = trx_purge(n_use_threads, srv_purge_batch_size,                               (++count % rseg_truncate_frequency) == 0);//每128次进行一次清理判断是否进入truncate流程  if (truncate || srv_upgrade_old_undo_found) { //truncate就是根据(++count % rseg_truncate_frequency)计算而来    trx_purge_truncate();  }

但是需要注意的count是一个static局部变量,因此每次调入函数会继续上次的取值继续计数。如果压力很小那么undo可能不能及时清理:

  • 小事务 如果都是小事务那么每个事务修改的undo page数可能达不到300个,那么必然需要等待128个事务才能进行一次清理。

  • 大事务 如果事务比较大,有许多undo page,那么超过了300*128 那么就会进行清理。

这不是说del flag记录不清理,而是说undo history链表不清理。因此我们经常看到History list length不为0的情况。

九、清理undo history和undo空间

这里简单记录其工作的流程。不做深入函数描述(能力有限)

清理undo history
调入如下:srv_purge_coordinator_thread ->srv_do_purge  ->trx_purge   ->trx_purge_truncate    ->trx_purge_truncate_history     ->trx_purge_truncate_rseg_history

清理的方式如下:

清理的起点:hdr_addr = trx_purge_get_log_from_hist(      flst_get_last(rseg_hdr + TRX_RSEG_HISTORY, &mtr));向上扫描: hdr_addr = prev_hdr_addr;结束条件:  if (undo_trx_no >= limit->trx_no) { //这里代表结束了    /* limit space_id should match the rollback segment    space id to avoid freeing if the page belongs to a    different rollback segment for the same trx_no. */    if (undo_trx_no == limit->trx_no &&        rseg->space_id == limit->undo_rseg_space) {      trx_undo_truncate_start(rseg, hdr_addr.page, hdr_addr.boffset,                              limit->undo_no);    }    rseg->unlatch();    mtr_commit(&mtr);    return;  }

值得注意的是这个清理过程不能大于oldest read view的 trx no,否则清理结束。

truncate undo流程
调入如下:srv_purge_coordinator_thread ->srv_do_purge  ->trx_purge   ->trx_purge_truncate    ->trx_purge_truncate_history     ->trx_purge_truncate_marked_undo

这之前有一个判定是否清理的过程

trx_purge_mark_undo_for_truncate ->Tablespace::needs_truncation

Tablespace::needs_truncation会判断是否进行undo truncate,这里涉及到两个参数

  • 参数innodb_undo_log_truncate的作用

  if (!srv_undo_log_truncate || m_rsegs == nullptr || m_rsegs->is_empty() ||      m_rsegs->is_init()) {    m_rsegs->s_unlock();    return (false); //如果没有开启undo truncate则不进行清理  }
  • 参数innodb_max_undo_log_size的作用

page_no_t trunc_size = ut_max(      static_cast
(srv_max_undo_tablespace_size / srv_page_size),      static_cast
(SRV_UNDO_TABLESPACE_SIZE_IN_PAGES)); //10MB  if (fil_space_get_size(id()) > trunc_size) { //如果undo tablespace大小大于了innodb_max_undo_log_size    return (true); //则进行清理  }

十、总结

到这里开头的问题我们基本就了解了,如下:

  • del flag在事务提交后,由协调线程判定是否能够进行清理,如果可以清理会分发给工作线程进行清理,这是一个异步的过程,如果修改数据比较多,那么这个过程可能比较慢,并且可以看到purge的相关线程压力较大,但是还算及时。

  • purge线程总会积压一段时间才会进行History list length的清理,如果是小事务(每次修改的page小于innodb_purge_batch_size的设置),那么需要128个这种小事务才清理一次,如果是大事务那么修改两超过了(innodb_purge_batch_size*innodb_purge_rseg_truncate_frequency)的设置则进行一次清理,但是不管如何这个指标持续不为0是正常。如果较大那么可能意味着要么有大查询,要么purge的各个线程满负荷工作。如下,9281为一个purge的工作线程:

并且purge线程状态处于running状态

  • purge的协调线程会在每次事务提交的时候醒来,判断是否有需要清理的事务,如果长期没有事务到来那么会第一次等待10ms,超时过后进入长时间的堵塞等待状态。

全文完。

Enjoy MySQL :)

扫码添加作者微信

叶老师的「MySQL核心优化」大课已升级到MySQL 8.0,扫码开启MySQL 8.0修行之旅吧

转载地址:http://htyk.baihongyu.com/

你可能感兴趣的文章
Metasploit CGI网关接口渗透测试实战
查看>>
Metasploit Web服务器渗透测试实战
查看>>
MFC模态对话框和非模态对话框
查看>>
Moment.js常见用法总结
查看>>
MongoDB出现Error parsing command line: unrecognised option ‘--fork‘ 的解决方法
查看>>
mxGraph改变图形大小重置overlay位置
查看>>
MongoDB可视化客户端管理工具之NoSQLbooster4mongo
查看>>
Mongodb学习总结(1)——常用NoSql数据库比较
查看>>
MongoDB学习笔记(8)--索引及优化索引
查看>>
mongodb定时备份数据库
查看>>
mppt算法详解-ChatGPT4o作答
查看>>
mpvue的使用(一)必要的开发环境
查看>>
MQ 重复消费如何解决?
查看>>
mqtt broker服务端
查看>>
MQTT 保留消息
查看>>
MQTT 持久会话与 Clean Session 详解
查看>>
MQTT工作笔记0007---剩余长度
查看>>
MQTT工作笔记0009---订阅主题和订阅确认
查看>>
Mqtt搭建代理服务器进行通信-浅析
查看>>
MS Edge浏览器“STATUS_INVALID_IMAGE_HASH“兼容性问题
查看>>