Purge sys

参考代码8.0.23,purge系统的目的是将不被任何read可见的旧版本数据(undo数据)进行清理。它包含两类处理线程:一类是一个purge coordinator thread,另一类是innodb_purge_threads-1个purge worker threads。

  • purge coordinator thread:清理删除记录,把purge任务放入队列,唤醒purge worker线程,还要truncate undolog
  • purge worker threads:只用于清理删除记录,处理完purge任务后则sleep待coordinator thread唤醒

mysql在srv_sys->sys_threads数组中维护内部线程,用于暂停或重启内部线程,purge coordinator thread存入SRV_PURGE_SLOT(1)位置,purge worker threads存入2 ~ srv_sys->n_sys_threads位置。

purge sys中有个purge_queue,它是个按Rollback segements的txn_no排序的最小堆。每次通过该purge_queue获取最早提交的事务所产生的undo log,然后进行清理。

Purge coordinator thread流程

purge coordinator thread在正常清理阶段,分批清理undo数据,每批最多清理300个undo log(具体的清理工作由purge worker thread执行)。由于清理期间依然有新事务在产生新的undo数据,清理一批后根据剩余待清理的undo log个数,来判断是要增加还是减少执行清理的worker线程个数。由于有innodb_purge_threads-1个purge worker threads,不执行清理的worker线程都处于sleep状态,要执行清理的worker线程会被purge coordinator thread唤醒,唤醒就是通过srv_sys->sys_threads数组中保存的slot->event进行的。

事务提交会将旧版本的数据放入undo segment中,其中普通表的旧版本数据放入redo rollback segment,临时表的旧版本数据放入unredo rollback segment,并将这两个rollback segment存入purge sys的purge_queue中。purge coordinator thread清理undo时,借助purge sys的purge_queue,就能确保取出的rollback segment是按照事务提交的顺序(txn_no)。

purge coordinator thread在每批清理前会先将事务系统中缓存的ReadView保存在purge_sys->view中,再按事务提交的顺序从purge sys->purge_queue中取出所有待清理的undo log,再确保待清理的undo log的trx_no不能超过purge_sys->view的m_low_limit_no(小于该值的undo log不再被该ReadView需要),然后按table_id进行分组存入map中,直到在map中存满300个undo log,如果当前rollback segment中不够,则会继续从purge sys的purge_queue中取出下一个rollback segment,重复该过程。

继续将map分组后的undo logs轮转存入清理任务purge_sys->query->thrs[i]中,例如map中包含3组undo logs,分别为table1、table2、table3,将table1的undo logs存入清理任务purge_sys->query->thrs[1],将table2的undo logs存入清理任务purge_sys->query->thrs[2],将table3的undo logs存入清理任务purge_sys->query->thrs[3]。再将其中前n_use_threads个清理任务存入srv_sys->tasks,并唤醒n_use_threads-1个purge worker thread,剩余一个清理任务交由purge coordinator thread来处理。

由于每批清理都只清理300个undo log,而一个rollback segment会包含多个undo log,所以需要在purge sys中记录上次清理的位置,也就是purge_sys->hdr_page_no和purge_sys->hdr_offset,这样下次清理时可以继续从上次上次清理的位置继续清理。

  1. srv_start_purge_threads
  2. v
  3. v
  4. srv_purge_coordinator_thread
  5. |
  6. |-> (while !srv_purge_should_exit) 正常清理阶段
  7. | v
  8. | srv_do_purge 把多余的线程当做一个池子,只有purge跟不上更新的时候,才会去调度这些线程
  9. | v
  10. | (while !srv_purge_should_exit)
  11. | |
  12. | |-> trx_sys->rseg_history_len相比上次purge有增长时,或者超过了innodb_max_purge_lag,则 ++n_use_threads
  13. | |
  14. | |-> 否则说明数据库处于不活跃状态(srv_check_activity), –-n_use_threads
  15. | |
  16. | |-> trx_purge
  17. | |
  18. | |-> trx_sys->mvcc->m_views中获取oldestReadView,拷贝到purge_sys->view,所有在该ReadView开启前提交的事务所产生的undo都被认为是可以清理的
  19. | |
  20. | |-> trx_purge_attach_undo_recs
  21. | | |
  22. | | |-> (for 300个待获取的undo log)
  23. | | | |
  24. | | | |-> trx_purge_fetch_next_rec 获取undo log
  25. | | | | |
  26. | | | | |-> (若purge_sys中未缓存上次清理的undo log)
  27. | | | | | v
  28. | | | | | trx_purge_choose_next_log 获取最早提交的事务所产生的undo log,然后进行清理
  29. | | | | | |
  30. | | | | | |-> TrxUndoRsegsIterator::set_next purge_sys->rseg_iter移到下一个有效的rseg
  31. | | | | | | |
  32. | | | | | | |-> purge_sys->purge_queue中取出trx_no最小的所有rsegs,存入purge_sys->rseg_iter->m_trx_undo_rsegs
  33. | | | | | | | 包含redo rollback segment(普通表)和noredo rollback segment(临时表)
  34. | | | | | | |
  35. | | | | | | |-> purge_sys->rseg指向m_trx_undo_rsegs中第一个rseg
  36. | | | | | | |
  37. | | | | | | |-> purge_sys->rseg_iter->m_iter 指向m_trx_undo_rsegs中下一个rseg
  38. | | | | | | |
  39. | | | | | | |-> 更新purge_sys->iter.trx_no & hdr_offset & hdr_page_no指向当前rseg上次purge到的位置
  40. | | | | | | |
  41. | | | | | | |-> purge_sys->next_stored = true
  42. | | | | | |
  43. | | | | | |-> trx_purge_read_undo_rec
  44. | | | | | |
  45. | | | | | |-> (!purge_sys->rseg->last_del_marks) 已读到该rseg的结尾
  46. | | | | | |
  47. | | | | | |-> (purge_sys->rseg->last_del_marks)
  48. | | | | | |
  49. | | | | | |-> trx_undo_get_first_rec 读取当前的undo log记录
  50. | | | | | |
  51. | | | | | |-> 更新purge_sys->offset & page_no & iter.undo_no iter.undo_rseg_space 指向当前的undo log
  52. | | | | |
  53. | | | | |-> purge_sys->iter.trx_no >= purge_sys->view.m_low_limit_no,则后续undo log不再清理
  54. | | | | |
  55. | | | | |-> trx_undo_build_roll_ptr 获取undo log的回滚段指针roll_ptr
  56. | | | | |
  57. | | | | |-> 获取undo log的对应事务的modifier_trx_id
  58. | | | | |
  59. | | | | |-> trx_purge_get_next_rec
  60. | | | | |
  61. | | | | |-> (若已读到该rseg的结尾) 获取下一个最早提交的事务所产生的undo log,然后进行清理
  62. | | | | | |
  63. | | | | | |-> trx_purge_rseg_get_next_history_log
  64. | | | | | |
  65. | | | | | |-> trx_purge_choose_next_log -> xxx
  66. | | | | |
  67. | | | | |-> (否则) trx_undo_page_get_next_rec 获取下一个undo log,并缓存在purge_sys
  68. | | | |
  69. | | | |-> undo logtable_id进行分组存入group_by map
  70. | | |
  71. | | |-> group_by map中,将table_id相同的一组undo log轮转存入清理任务purge_sys->query->thrs[i]中
  72. | |
  73. | |-> (for n_use_threads-1 worker线程)
  74. | | |
  75. | | |-> que_fork_scheduler_round_robin purge_sys->query->thrs取出一个清理任务
  76. | | | v
  77. | | | thr->state = QUE_THR_RUNNING
  78. | | |
  79. | | |-> srv_que_task_enqueue_low 将该清理任务存入srv_sys->tasks
  80. | | v
  81. | | srv_release_threads 唤醒一个worker线程,它会从srv_sys->tasks中取出一个清理任务
  82. | |
  83. | |-> que_fork_scheduler_round_robin
  84. | |
  85. | |-> trx_purge_truncate 128purge, truncate一次history list,所以每隔一会,才看到history list长度变小
  86. | v
  87. | trx_purge_truncate_history
  88. | |
  89. | |-> (for rseg in 所有undo space中的rsegs)
  90. | v
  91. | trx_purge_truncate_rseg_history
  92. | v
  93. | trx_purge_remove_log_hdr rseghistory list中删除该undo log
  94. |
  95. |-> (while srv_fast_shutdown == 0 && n_pages_purged > 0) slow shutdown要避免在退出上述循环后,有新的记录加入
  96. | v
  97. | trx_purge -> xxx
  98. |
  99. |-> trx_purge(truncate=true) -> xxx 最后对history list做一次truncate,并确保所有worker线程退出

Purge worker thread流程

purge worker thread被唤醒后,会从srv_sys->tasks中取出一个清理任务,清理掉其中的undo logs(具有相同的table id)。对于每个待清理的undo log,会先解析undo log内容,检查该undo log是否能进行清理,若能清理,先删除二级索引再删除clustered索引。

  1. srv_worker_thread
  2. v
  3. (while purge_sys->state != PURGE_STATE_EXIT) worker线程总是在coordinator线程退出之后再退出
  4. v
  5. srv_task_execute
  6. |
  7. |-> 持有srv_sys->tasks_mutex锁,从srv_sys->tasks中取出一个thr,然后释放tasks_mutex
  8. |
  9. |-> que_run_threads
  10. |
  11. |-> que_run_threads_low
  12. |
  13. |-> que_thr_node_step
  14. |
  15. |-> row_purge_step 从清理任务thr中取出undo logs(具有相同的table id),进行清理
  16. |
  17. |-> row_purge
  18. | v
  19. | (for undo_rec in 清理任务thr中的所有undo logs)
  20. | |
  21. | |-> row_purge_parse_undo_rec 解析undo log,检查该undo log是否能进行清理
  22. | |
  23. | |-> row_purge_record 清理undo log
  24. | v
  25. | row_purge_del_mark 先删除二级索引再删除clustered索引
  26. | |
  27. | |-> row_purge_remove_multi_sec_if_poss
  28. | |
  29. | |-> row_purge_remove_clust_if_poss
  30. |
  31. |-> row_purge_end

参考:
InnoDB事务系统
InnoDB的read view,回滚段和purge过程简介