久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

MySQL中Innodb page clean線程分析

146次閱讀
沒有評論

共計 10661 個字符,預計需要花費 27 分鐘才能閱讀完成。

這篇文章主要講解了“MySQL 中 Innodb page clean 線程分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著丸趣 TV 小編的思路慢慢深入,一起來研究和學習“MySQL 中 Innodb page clean 線程分析”吧!

一、數據結構和入口函數 1、數據結構

page_cleaner_t:整個 Innodb 只有一個,包含整個 page clean 線程相關信息。其中包含了一個 page_cleaner_slot_t 的指針。

變量名含義 mutex 用于保護整個 page_cleaner_t 結構體和 page_cleaner_slot_t 結構體,當需要修改結構體信息的時候需要獲取這個 mutex,如在 pc_request 函數中 is_requested 一個條件變量,用于喚醒堵塞在這個條件之上的工作線程 is_finished 一個條件變量,用于通知協調線程刷新工作已經完成 n_workers 當前存在的工作線程總數 requested 布爾值,當前是否需要進行臟數據刷新工作 lsn_limit 需要刷新到 lsn 的位置,當需要同步刷新的時候,這個值將被賦予,以保證小于這個 lsn 的日志都已經完成了刷盤工作 n_slots 槽的數量,槽的數量和 buffer instance 的數量相同 n_slots_requested 當前處于需要刷新狀態下 (PAGE_CLEANER_STATE_REQUESTED) 的槽的數量 n_slots_flushing 當前處于刷新狀態下 (PAGE_CLEANER_STATE_FLUSHING) 的槽的數量 n_slots_finished 當前處于已經刷新完成狀態下 (PAGE_CLEANER_STATE_FINISHED) 的槽的數量 flush_time 整個 (以 innodb buffer 為單位) 刷新消耗的時間(累計 page_cleaner- flush_time += ut_time_ms() – tm;)flush_pass 整個 (以 innodb buffer 為單位) 刷新的次數(累計 page_cleaner- flush_pass++;)slots 指針指向實際的槽 is_running 布爾值,如果關閉 innodb 會被設置為 false,進行強行刷新臟數據

page_cleaner_slot_t:每個 buffer instance 都包含一個這樣的結構體,page clean 工作線程刷新的時候每個線程都會輪詢的檢測每個槽,知道找到沒有被其他 page clean 線程刷新的槽進行刷新工作,直到每個槽(buffer instance)都刷新完成。參考 pc_flush_slot 函數。

變量名含義 state 狀態 PAGE_CLEANER_STATE_REQUESTED、PAGE_CLEANER_STATE_FLUSHING 和 PAGE_CLEANER_STATE_FINISHED 中的一種 n_pages_requested 本槽需要刷新的總的塊數量 n_flushed_list 已經刷新的塊數 succeeded_list 布爾值,刷新是否完成 flush_list_time 本槽刷新消耗的時間(累計參考 pc_flush_slot 函數)flush_list_pass 本槽進行刷新操作的次數(累計參考 pc_flush_slot 函數)2、入口函數

協調工作線程入口:buf_flush_page_cleaner_coordinator

工作線程入口:buf_flush_page_cleaner_worker

二、主循環解析

其由函數 buf_flush_page_cleaner_coordinator 實現。實際正常運行情況下的工作都包含在 while (srv_shutdown_state == SRV_SHUTDOWN_NONE) 這個大循環下。

1、是否需要睡眠 1 秒判斷

首先如果沒有活躍的 change buffer 并且沒有 pending 的物理塊,并且上次刷新的塊數量為 0
則不需要睡眠 1 秒:

if (srv_check_activity(last_activity) 
 || buf_get_n_pending_read_ios() || n_flushed == 0){
 ret_sleep = pc_sleep_if_needed( next_loop_time, sig_count); // 睡眠一秒  if (srv_shutdown_state != SRV_SHUTDOWN_NONE) { break;
 }
 } else if (ut_time_ms()   next_loop_time) { // 如果當前時間大于   上次刷新   時間 +1  秒則   設置為 OS_SYNC_TIME_EXCEEDED
 ret_sleep = OS_SYNC_TIME_EXCEEDED; 
 } else {
 ret_sleep = 0;
 }

但是這個睡眠是可以被喚醒的,比如同步刷新應該就會喚醒它(buf_flush_request_force 函數)。參考函數 os_event::wait_time_low

2、IO 能力不足警告

如前文所描述這里產生如下警告:

page_cleaner: 1000ms intended loop took **ms. The settings might not be optimal.((flushed= **  , during the time.)

源碼片段:

if (curr_time   next_loop_time + 3000) { // 如果刷新時間   大于了   上次時間  +1  秒 +3  秒   則報 info
 if (warn_count == 0) { ib::info()    page_cleaner: 1000ms 
   intended loop took  
   1000 + curr_time
 - next_loop_time
    ms. The settings might not 
   be optimal. (flushed= 
   n_flushed_last
    , during the time.)  if (warn_interval   300) {
 warn_interval = 600;
 } else {
 warn_interval *= 2;
 }

3、同步刷新判斷

觸發條件

(ret_sleep != OS_SYNC_TIME_EXCEEDED
   srv_flush_sync
   buf_flush_sync_lsn   0)

同步會喚醒正在睡眠狀態的 page clean 協調工作線程那么睡眠應該不會滿足一秒的條件所以不會被標記為 OS_SYNC_TIME_EXCEEDED,同時 srv_flush_sync 和 buf_flush_sync_lsn 均會被設置接下來就是喚醒工作線程進行刷新,同時本協調線程也完成部分任務。

工作代碼

 pc_request(ULINT_MAX, lsn_limit); // 喚醒 page clean  工作線程干活
 /* Coordinator also treats requests */ // 協調者同樣要完成部分任務
 while (pc_flush_slot()   0) {}

喚醒操作

如前文描述在 checkpoint 或者 DML 語句執行過程中都會通過 log_free_check 檢查是否 redo log 處于安全的狀態,如果不安全就會調用如下代碼(log_preflush_pool_modified_pages 函數中)喚醒 page clean 線程進行同步刷新:

if (srv_flush_sync) { /* wake page cleaner for IO burst */
 buf_flush_request_force(new_oldest); // 設置全局變量同時通過 broadcast 喚醒同步刷新
 }
 buf_flush_wait_flushed(new_oldest); // 所有線程等待同步刷新完成

4、活躍刷新

觸發條件

srv_check_activity(last_activity)

這里判斷是否有活躍的線程,所謂活躍就是調用 srv_inc_activity_count 函數進行增加的,一般來講 DML 和 DDL 會標記為活躍,purge 線程及其工作線程工作期間會標記為活躍。可以將斷點做到 srv_inc_activity_count 進行 debug。所以線上數據庫 DML 比較多所以一般都會是活躍刷新。

工作代碼

這里涉及到刷新多少個塊計算主要函數為 page_cleaner_flush_pages_recommendation,后面在討論。

n_to_flush = page_cleaner_flush_pages_recommendation(lsn_limit, last_pages);// 此處 n_to_flush 就是本次需要刷新的塊數的數量 pc_request(n_to_flush, lsn_limit); // 喚醒 page clean  工作線程干活 /* Coordinator also treats requests */ // 工作協調線程同樣要完成部分任務
 while (pc_flush_slot()   0) {}
pc_wait_finished(n_flushed_list);// 等待其他刷新完成

5、空閑刷新

觸發條件

else if (ret_sleep == OS_SYNC_TIME_EXCEEDED)

當睡足了 1 秒,并且沒有活躍的線程。那么就進行空閑刷新,一般來講如果沒有 DML/DDL 等語句那么應該進行是空閑刷新。

工作代碼

buf_flush_lists(PCT_IO(100), LSN_MAX,  n_flushed); //io 能力   刷新到那個 lsn  以及傳出刷新的塊數量 //PCT_IO 是一個宏如下:#define PCT_IO(p) ((ulong) (srv_io_capacity * ((double) (p) / 100.0)))

可以看到這里的百分比直接是 100% 及按照 innodb_io_capacity 參數的設定進行刷新。

當然這里只是看了正常期間工作的代碼,如果是 Innodb shutdown 也會觸發同步刷新。可自行參考代碼。

三、page_cleaner_flush_pages_recommendation 函數

前面提過這個函數,是活躍刷新刷新塊的計算函數,下面直接給出整個代碼

{ cur_lsn = log_get_lsn();// 獲取當前的 lsn  在  redo buffer 中的
 if (prev_lsn == 0) { // 靜態變量如果是 0 則代表是第一次執行本函數
 /* First time around. */
 prev_lsn = cur_lsn;
 prev_time = ut_time(); // 獲取當前時間
 return(0);
 } if (prev_lsn == cur_lsn) { // 如果沒有 redo 日志生成
 return(0);
 }
 sum_pages += last_pages_in; time_t curr_time = ut_time(); double time_elapsed = difftime(curr_time, prev_time);
 avg_page_rate = static_cast ulint ( ((static_cast double (sum_pages)
 / time_elapsed)
 + avg_page_rate) / 2); // 算出上次刷新每秒刷新的 pages 數量,同時加上次計算的每秒平均刷新塊數   然后除以 2   得到一個每秒刷新的 pages 數量  !!!第一個計算條件 avg_page_rate  生成
 /* How much LSN we have generated since last call. */
 lsn_rate = static_cast lsn_t ( static_cast double (cur_lsn - prev_lsn)
 / time_elapsed);// 計算 redo lsn 生成率
 lsn_avg_rate = (lsn_avg_rate + lsn_rate) / 2;// 計算 redo 每秒平均生成率
 /* aggregate stats of all slots */
 mutex_enter(page_cleaner- mutex);
 ulint flush_tm = page_cleaner- flush_time;
 ulint flush_pass = page_cleaner- flush_pass;
 page_cleaner- flush_time = 0;
 page_cleaner- flush_pass = 0;
 ulint list_tm = 0;
 ulint list_pass = 0; for (ulint i = 0; i   page_cleaner- n_slots; i++) {// 掃描所有的槽
 page_cleaner_slot_t* slot;
 slot =  page_cleaner- slots[i];
 list_tm += slot- flush_list_time;
 list_pass += slot- flush_list_pass;
 slot- flush_list_time = 0;
 slot- flush_list_pass = 0;
 }
 mutex_exit(page_cleaner- mutex);
 oldest_lsn = buf_pool_get_oldest_modification(); // 獲取 flush list 中最老的 ls
 ut_ad(oldest_lsn  = log_get_lsn());// 斷言
 age = cur_lsn   oldest_lsn ? cur_lsn - oldest_lsn : 0; // 獲取當前 LSN 和最老 LSN 的之間的差值
 pct_for_dirty = af_get_pct_for_dirty(); // 計算出一個刷新百分比  (比如 100) !!!! 重點
 pct_for_lsn = af_get_pct_for_lsn(age);// 計算出 lsn 的比率   百分比(l 列如 4.5) 
 pct_total = ut_max(pct_for_dirty, pct_for_lsn);// 取他們的大值
 
 /* Estimate pages to be flushed for the lsn progress */// 計算 target_lsn
 ulint sum_pages_for_lsn = 0; lsn_t target_lsn = oldest_lsn
 + lsn_avg_rate * buf_flush_lsn_scan_factor; // 計算下一次刷新的   目標 lsn  及 target_lsnbuf_flush_lsn_scan_factor 是定值 3
 for (ulint i = 0; i   srv_buf_pool_instances; i++) {// 循環整個 buffer instance 找到小于 target_lsn 的臟塊
 buf_pool_t* buf_pool = buf_pool_from_array(i);
 ulint pages_for_lsn = 0;
 buf_flush_list_mutex_enter(buf_pool); for (buf_page_t* b = UT_LIST_GET_LAST(buf_pool- flush_list);// 每個 innodb buffer 的末尾的 flush list  進行掃描,頭插法?
 b != NULL;
 b = UT_LIST_GET_PREV(list, b)) { if (b- oldest_modification   target_lsn) { break;
 }
 ++pages_for_lsn; // 某個  innodb buffer  實例中  flush list  小于這個  target lsn  的  page 計數
 }
 buf_flush_list_mutex_exit(buf_pool);
 sum_pages_for_lsn += pages_for_lsn; // 這里匯總所有  innodb buffer 實例中  flush list  小于這個  target lsn  的  page  總數
 mutex_enter(page_cleaner- mutex);
 ut_ad(page_cleaner- slots[i].state
 == PAGE_CLEANER_STATE_NONE);// 斷言所有的槽處于沒有刷新狀態
 page_cleaner- slots[i].n_pages_requested
 = pages_for_lsn / buf_flush_lsn_scan_factor + 1; // 確認槽的 n_pages_requested 值
 mutex_exit(page_cleaner- mutex);
 }
 sum_pages_for_lsn /= buf_flush_lsn_scan_factor;//buf_flush_lsn_scan_factor 為定值 3
 /* Cap the maximum IO capacity that we are going to use by
 max_io_capacity. Limit the value to avoid too quick increase */
 n_pages = PCT_IO(pct_total); // 根據   前面得到的  pct_total  和  srv_io_capacity 參數得到   刷新的塊數  !!! 第二個計算參數生成。 if (age   log_get_max_modified_age_async()) { // 如果日質量小于   異步刷新的范疇
 ulint pages_for_lsn = std::min ulint (sum_pages_for_lsn,
 srv_max_io_capacity * 2); // 即便是需要刷新的塊數很多,最多只能刷 max_io_capacity* 2 的數量!!! 第三個計算參數生成
 n_pages = (n_pages + avg_page_rate + pages_for_lsn) / 3; // 3 部分組成  1、根據參數計算出來的 IO 能力  2、以往每秒刷新頁的數量  3、根據 target lsn  計算出來的一個需要刷新的塊數
 } if (n_pages   srv_max_io_capacity) {
 n_pages = srv_max_io_capacity;
 } return(n_pages);
}

此函數最后計算出了需要刷新的塊,其中刷新比率計算的的重點函數為 af_get_pct_for_dirty 和 af_get_pct_for_lsn 下面將給出代碼注釋,其實前文中的算法就來自 af_get_pct_for_dirty。

四、af_get_pct_for_dirty 和 af_get_pct_for_lsn 函數

af_get_pct_for_dirty 函數

 double dirty_pct = buf_get_modified_ratio_pct(); // 得到   修改的塊 / 總的塊的   的百分比   記住臟數據比率
 if (dirty_pct == 0.0) { /* No pages modified */
 return(0);
 }
 ut_a(srv_max_dirty_pages_pct_lwm
  = srv_max_buf_pool_modified_pct); if (srv_max_dirty_pages_pct_lwm == 0) { // 如果 innodb_max_dirty_pages_pct_lwm 沒有設置
 /* The user has not set the option to preflush dirty
 pages as we approach the high water mark. */
 if (dirty_pct  = srv_max_buf_pool_modified_pct) { // 如果臟數據比率大于了 innodb_max_dirty_pages_pct 則返回比率 100%
 /* We have crossed the high water mark of dirty
 pages In this case we start flushing at 100% of
 innodb_io_capacity. */
 return(100);
 }
 } else if (dirty_pct  = srv_max_dirty_pages_pct_lwm) { // 如果設置了 innodb_max_dirty_pages_pct_lwm  并且臟數據比率大于了
 /* We should start flushing pages gradually. */ //innodb_max_dirty_pages_pct_lwm 參數設置
 return(static_cast ulint ((dirty_pct * 100)
 / (srv_max_buf_pool_modified_pct + 1))); // 則返回  (臟數據比率 /(innodb_max_dirty_pages_pct+1))*100  也是一個比率   如(45/76)*100
 } return(0);// 否則返回 0 

af_get_pct_for_lsn 函數:

注意 innodb_cleaner_lsn_age_factor 參數默認設置為 high_checkpoint,可以看到算法最后是除以 700.5,所有前文我說這個函數算出來的比率一般比較小。

 lsn_t af_lwm = (srv_adaptive_flushing_lwm
 * log_get_capacity()) / 100;// srv_adaptive_flushing_lwm=10  那么大約就是  logtotalsize*(9/10)*(1/10) 943349  計算一個 low water mark
 if (age   af_lwm) { // 如果當前生成的 redo  小于了  low water master  則返回 0   也就是說  redo 日志量生成量不高則不需要權衡
 /* No adaptive flushing. */ // 可以看出這里和 redo 設置的大小有關,如果 redo 文件設置越大則 af_lwm 越大,觸發權衡的機率越小
 return(0);
 }
 max_async_age = log_get_max_modified_age_async(); // 獲取需要異步刷新的的位置   大約為 logtotalsize*(9/10)*(7/8)
 if (age   max_async_age   !srv_adaptive_flushing) { // 如果小于異步刷新   且   自適應 flush  沒有開啟
 /* We have still not reached the max_async point and
 the user has disabled adaptive flushing. */
 return(0);
 } /* If we are here then we know that either:
 1) User has enabled adaptive flushing
 2) User may have disabled adaptive flushing but we have reached
 max_async_age. */
 lsn_age_factor = (age * 100) / max_async_age; // 比率 lsn_age_factor = (本次刷新的日志量 /(logtotalsize*(9/10)*(7/8)))
 ut_ad(srv_max_io_capacity  = srv_io_capacity); 
 switch ((srv_cleaner_lsn_age_factor_t)srv_cleaner_lsn_age_factor) { case SRV_CLEANER_LSN_AGE_FACTOR_LEGACY: return(static_cast ulint ( ((srv_max_io_capacity / srv_io_capacity)
 * (lsn_age_factor
 * sqrt((double)lsn_age_factor)))
 / 7.5)); //430
 case SRV_CLEANER_LSN_AGE_FACTOR_HIGH_CHECKPOINT: //innodb_cleaner_lsn_age_factor 參數默認設置為 high_checkpoint
 return(static_cast ulint ( 
 ((srv_max_io_capacity / srv_io_capacity) // ((max_io_cap /io_cap) * (sqrt(lsn_age_factor)*lsn_age_factor*lsn_age_factor))/700.5
 * (lsn_age_factor * lsn_age_factor //(10 * (3.3*10*10))/700 =4.3
 * sqrt((double)lsn_age_factor)))
 / 700.5)); //

感謝各位的閱讀,以上就是“MySQL 中 Innodb page clean 線程分析”的內容了,經過本文的學習后,相信大家對 MySQL 中 Innodb page clean 線程分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是丸趣 TV,丸趣 TV 小編將為大家推送更多相關知識點的文章,歡迎關注!

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-07-24發表,共計10661字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 中牟县| 河源市| 攀枝花市| 蛟河市| 班玛县| 台前县| 龙海市| 开平市| 南宁市| 宜丰县| 民和| 遂平县| 安徽省| 镇康县| 潍坊市| 班戈县| 辽宁省| 县级市| 五河县| 淅川县| 车致| 通化县| 福清市| 岳西县| 册亨县| 香港 | 宕昌县| 宁海县| 兴仁县| 西吉县| 土默特右旗| 荃湾区| 海城市| 肃南| 和静县| 沙洋县| 休宁县| 民勤县| 聂荣县| 莱阳市| 额敏县|