本文主要是記錄innodb在初始化日志子系統時,如何計算異步/同步刷髒或checkpoint的臨界範圍
代碼分析基于mysql 5.6.11
///////////////////////////////////////////////////////////////////////////////////////////////
相關配置為:
innodb_log_buffer_size=200m
innodb_log_file_size=1000m
innodb_log_files_in_group=4
innodb_flush_log_at_trx_commit = 1
innodb_thread_concurrency = 64
刷髒和做checkpoint的臨界條件在系統啟動初始化日志系統時即被确定了,backtrace 如下:
innobase_start_or_create_for_mysql
->log_group_init
2151 log_group_init(0, i, srv_log_file_size * univ_page_size,
2152 srv_log_space_first_id,
2153 srv_log_space_first_id + 1);
->log_calc_max_ages
log_calc_max_ages:
1.計算目前redo log日志組的最大容納lsn(log_group_get_capacity):
smallest_capacity = (group->file_size – log_file_hdr_size) * group->n_files = (innodb_log_file_size-4*512)*4 = 4194295808
smallest_capacity = smallest_capacity – smallest_capacity / 10; // 為了安全起見,保留10分之一的空間
2.檢查在目前thread concurrency(srv_thread_concurrency)限制下,日志組是否擁有足夠的空閑空間:
free = log_checkpoint_free_per_thread * (10 + srv_thread_concurrency)+ log_checkpoint_extra_free = (4*16384) * (10 + 64) + 8*16384 = 4980736
如果free > smallest_capacity /2的話,那麼就會錯誤退出,表明innodb_log_file_size設的太小了。
否則,預留邊界:
margin = smallest_capacity – free;
margin = margin – margin / 10 = 3392896943;
實際上margin的計算可以總結為:
margin = {
[ (innodb_log_file_size-log_file_hdr_size) * innodb_log_files_in_group ] * (9/10)
–
[ log_checkpoint_free_per_thread * (10 + innodb_thread_concurrency) + log_checkpoint_extra_free]
} * (9/10)
log_sys->log_group_capacity = smallest_capacity = 3774866228;
log_sys->max_modified_age_async = margin– margin / log_pool_preflush_ratio_async = margin * (7/8) = 2968784826 //當lsn -buf_pool_get_oldest_modification()超過這個值時, 需要做異步刷髒頁
log_sys->max_modified_age_sync = margin– margin / log_pool_preflush_ratio_sync = margin *(15/16) = 3180840885 //當lsn -buf_pool_get_oldest_modification()超過這個值時, 需要做同步刷髒頁
log_sys->max_checkpoint_age_async = margin – margin
/ log_pool_checkpoint_ratio_async = margin *(31/32) = 3286868914 //當lsn – last_checkpoint_lsn超過該值時,需要做一次異步checkpoint
log_sys->max_checkpoint_age = margin = 3392896943 //lsn – last_checkpoint_lsn不允許超過該值,否則需要做同步checkpoint
上面這幾個變量,除了第一個之外,都是用于在函數log_checkpoint_margin中進行判斷,log_sys->check_flush_or_checkpoint是判斷是否flush/checkpoint的關鍵變量
主要判斷邏輯如下:
1.如果log_sys->check_flush_or_checkpoint為false,直接傳回,表明不需要flush或者checkpoint
2.找出目前所有buffer pool執行個體中最老的oldesrt_lsn,從每個bp->flush_list的尾部讀取(log_buf_pool_get_oldest_modification())
3.如果log->lsn – oldest_lsn大于log_sys->max_modified_age_sync,表明需要做一次同步重新整理,将lsn 推進到新的lsn位置(log_preflush_pool_modified_pages):
lsn範圍為:2 * (log->lsn – oldest_lsn – log_sys->max_modified_age_sync) + oldest_lsn
如果重新整理失敗,表明同時有别的線程也在刷髒,則将check_flush_or_checkpoint設定為true,回到第一步
4.判斷是否需要做checkpoint
checkpoint_age = log->lsn – log->last_checkpoint_lsn;
如果checkpoint_age > log_sys->max_checkpoint_age, 表示需要做一次同步checkpoint;
如果 log_sys->max_checkpoint_age_async < checkpoint_age <= log_sys->max_checkpoint_age ,表示需要做一次異步的checkpoint,并設定log_sys->check_flush_or_checkpoint = false;
如果checkpoint_age <=log_sys->max_checkpoint_age_async ,則無需做checkpoint,并設定log_sys->check_flush_or_checkpoint = false;
checkpoint 調用函數log_checkpoint(checkpoint_sync, false); 如果是同步checkpoint(checkpoint_sync為true) 還要傳回到第1步繼續判斷
另外,這幾個變量在函數log_close中會被用到,它會去做一件重要的事情:設定log_sys->check_flush_or_checkpoint。
至于異步刷髒,log_sys->max_modified_age_async被封裝在函數log_get_max_modified_age_async中, 被函數af_get_pct_for_lsn。顯而易見,異步刷髒是由page cleaner線程來完成的。
在函數af_get_pct_for_lsn中,根據目前的lsn,計算需要以io capacity的百分之幾來刷髒
當目前lsn-buf_pool_get_oldest_modification()超過log_sys->max_modified_age_async時:
age = log_sys->lsn – buf_pool_get_oldest_modification()
lsn_age_factor = (age *100)/log_sys->max_modified_age_async )
傳回值為:
pct = [
(innodb_io_capacity_max/innodb_io_capacity)
*
(lsn_age_factor * sqrt((double)lsn_age_factor))
] /7.5
另外,如果參數innodb_adaptive_flushing設定為off,且沒有超過log_get_max_modified_age_async()的話,直接傳回0
page cleaner線程會同時根據lsn 和髒頁比例來擷取pct,并取其中的最大值。