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中获取oldest的ReadView,拷贝到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 log的table_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          每128次purge, 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      从rseg的history 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过程简介

原文:http://mysql.taobao.org/monthly/2022/03/01/