在做5.6.12 vs 5.6.11的性能对比时,大量update产生了很长的purge history list。手贱把innodb_fast_shutdowns设置为0了,结果Purge线程一直干活了,差不多两个小时才结束….
我们知道,在MySQL5.5版本中,就已经开始将purge 任务从master线程中独立出来,而到了5.6,已经支持多个purge线程同时进行,简单的理了下代码逻辑.
////////////////////////////////////////////////////////
在5.6中,提供了参数Innodb_purge_threads来控制做purge操作的后台线程数,最大允许设置为32.
purge线程被分为两类,一类是coordinator thread,只有一个这样的线程,另外的Innodb_purge_threads-1个是worker线程.
线程在Innodb启动时创建
quoted code in innobase_start_or_create_for_mysql:
2584 if (!srv_read_only_mode 2585 && srv_force_recovery < SRV_FORCE_NO_BACKGROUND) { 2586 2587 os_thread_create( 2588 srv_purge_coordinator_thread, 2589 NULL, thread_ids + 5 + SRV_MAX_N_IO_THREADS); 2590 2591 ut_a(UT_ARR_SIZE(thread_ids) 2592 > 5 + srv_n_purge_threads + SRV_MAX_N_IO_THREADS); 2593 2594 /* We've already created the purge coordinator thread above. */ 2595 for (i = 1; i < srv_n_purge_threads; ++i) { 2596 os_thread_create( 2597 srv_worker_thread, NULL, 2598 thread_ids + 5 + i + SRV_MAX_N_IO_THREADS); 2599 } 2600 2601 srv_start_wait_for_purge_to_start(); 2602 2603 } else { 2604 purge_sys->state = PURGE_STATE_DISABLED; 2605 }
a. coordinator thread
协调线程的入口函数是srv_purge_coordinator_thread
分为三个阶段:
正常工作阶段,在一个while循环中调用:
2754 rseg_history_len = srv_do_purge( 2755 srv_n_purge_threads, &n_total_purged);
从while break的条件由函数srv_purge_should_exit确定,这里有一个特殊情况,当innodb_fast_shutdown设置为0时,如果上一次purge的Page数不为0,则返回false,表示不退出循环,这是因为当fast shutdown为0时,需要做完所有的purge操作才会结束线程任务
第二个阶段是确认innodb_fast_shutdown被设置为0时,所有的记录都被purge掉了;这可以避免在退出上述循环后,有新的记录加入。这里已经不再使用worker线程了(trx_purge的第一个参数为1)
2769 while (srv_fast_shutdown == 0 && n_pages_purged > 0) { 2770 n_pages_purged = trx_purge(1, srv_purge_batch_size, false); 2771 }
最后对history list做一次truncate,并确保所有worker线程退出
2773 /* Force a truncate of the history list. */ 2774 n_pages_purged = trx_purge(1, srv_purge_batch_size, true);
这里有两个需要关注的函数:
a1)srv_do_purge:
在协调线程的主要工作都是在这个函数中,注意,不是有多少工作线程,就会用多少的,这里 实际上是把多余的线程当做一个池子,只有purge跟不上更新的时候,才会去调度这些线程:
调整n_use_threads
>>当trx_sys->rseg_history_len相比上次purge有增长时,或者超过了innodb_max_purge_lag_delay(不为0),++n_use_threads
>>否则,如果存在activity(srv_check_activity),–n_use_threads
执行purge调度
2577 n_pages_purged = trx_purge( 2578 n_use_threads, srv_purge_batch_size, false);
每128次purge, truncate一次history list,这也是为什么我们每隔一会,才看到history list长度变小的原因
2580 if (!(count++ % TRX_SYS_N_RSEGS)) { 2581 /* Force a truncate of the history list. */ 2582 n_pages_purged += trx_purge( 2583 1, srv_purge_batch_size, true); 2584 }
a2) trx_purge
trx_purge是purge任务调度的核心函数,包含三个参数:
* n_purge_threads —>使用到的worker线程数
* batch_size —-> 由innodb_purge_batch_size控制,表示一次Purge的记录数
* truncate —>是否truncate history list
持有purge_sys->latch的x锁,并建立read view,然后释放锁
purge_sys->view = read_view_purge_open(purge_sys->heap);
读取需要purge的undo记录,记录数不超过batch size,这些purge任务被指派给n_purge_threads个thr
1213 /* Fetch the UNDO recs that need to be purged. */ 1214 n_pages_handled = trx_purge_attach_undo_recs( 1215 n_purge_threads, purge_sys, &purge_sys->limit, batch_size);
当n_purge_threads>1时,会将n_purge_threads-1个thr加入到工作队列,由worker线程来消费
1221 /* Submit the tasks to the work queue. */ 1222 for (i = 0; i < n_purge_threads - 1; ++i) { 1223 thr = que_fork_scheduler_round_robin( 1224 purge_sys->query, thr); 1225 1226 ut_a(thr != NULL); 1227 1228 srv_que_task_enqueue_low(thr); 1229 }
调度线程同样也需要运行一个thr任务,而不是仅仅只做分配;完成后,还需要等待其他worker线程完成任务
1243 run_synchronously: 1244 ++purge_sys->n_submitted; 1245 1246 que_run_threads(thr); 1247 1248 os_atomic_inc_ulint( 1249 &purge_sys->bh_mutex, &purge_sys->n_completed, 1); 1250 1251 if (n_purge_threads > 1) { 1252 trx_purge_wait_for_workers_to_complete(purge_sys); 1253 }
当truncate为true时,还需要调用trx_purge_truncate–>trx_purge_truncate_history从回滚段中移除历史记录。
b.worker thread
入口函数是srv_worker_thread
2473 do { 2474 srv_suspend_thread(slot); 2475 2476 os_event_wait(slot->event); 2477 2478 if (srv_task_execute()) { 2479 2480 /* If there are tasks in the queue, wakeup 2481 the purge coordinator thread. */ 2482 2483 srv_wake_purge_thread_if_not_active(); 2484 } 2485 2486 /* Note: we are checking the state without holding the 2487 purge_sys->latch here. */ 2488 } while (purge_sys->state != PURGE_STATE_EXIT);
purge_sys->state是协调线程在将要退出前设置成PURGE_STATE_EXIT。因此worker线程总是在coordinator线程退出之后再退出
srv_task_execute()的工作流程也很简单: 持有srv_sys->tasks_mutex锁,从srv_sys->tasks中取出一个thr,然后释放tasks_mutex。再调用que_run_threads(thr)完成purge