MySQL中Innodb page clean线程分析
这篇文章主要讲解了“MySQL中Innodb page clean线程分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“MySQL中Innodb page clean线程分析”吧!
一、数据结构和入口函数1、数据结构page_cleaner_t:整个Innodb只有一个,包含整个page clean线程相关信息。其中包含了一个page_cleaner_slot_t的指针。
page_cleaner_slot_t:每个buffer instance都包含一个这样的结构体,page clean工作线程刷新的时候每个线程都会轮询的检测每个槽,知道找到没有被其他page clean线程刷新的槽进行刷新工作,直到每个槽(buffer instance )都刷新完成。参考pc_flush_slot函数。
协调工作线程入口: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;}}elseif(ut_time_ms()>next_loop_time){//如果当前时间大于上次刷新时间+1秒则设置为OS_SYNC_TIME_EXCEEDEDret_sleep=OS_SYNC_TIME_EXCEEDED;}else{ret_sleep=0;}
但是这个睡眠是可以被唤醒的,比如同步刷新应该就会唤醒它(buf_flush_request_force函数)。参考函数os_event::wait_time_low
2、IO能力不足警告如前文所描述这里产生如下警告:
page_cleaner:1000msintendedlooptook**ms.Thesettingsmightnotbeoptimal.((flushed="**",duringthetime.)
源码片段:
if(curr_time>next_loop_time+3000){//如果刷新时间大于了上次时间+1秒+3秒则报infoif(warn_count==0){ib::info()<<"page_cleaner:1000ms""intendedlooptook"<<1000+curr_time-next_loop_time<<"ms.Thesettingsmightnot""beoptimal.(flushed="<<n_flushed_last<<",duringthetime.)";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);//唤醒pageclean工作线程干活/*Coordinatoralsotreatsrequests*///协调者同样要完成部分任务while(pc_flush_slot()>0){}
唤醒操作
如前文描述在checkpoint或者DML语句执行过程中都会通过log_free_check检查是否redo log处于安全的状态,如果不安全就会调用如下代码(log_preflush_pool_modified_pages函数中)唤醒page clean线程进行同步刷新:
if(srv_flush_sync){/*wakepagecleanerforIOburst*/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);//唤醒pageclean工作线程干活/*Coordinatoralsotreatsrequests*///工作协调线程同样要完成部分任务while(pc_flush_slot()>0){}pc_wait_finished(&n_flushed_list);//等待其他刷新完成5、空闲刷新
触发条件
elseif(ret_sleep==OS_SYNC_TIME_EXCEEDED)
当睡足了1秒,并且没有活跃的线程。那么就进行空闲刷新,一般来讲如果没有DML/DDL等语句那么应该进行是空闲刷新。
工作代码
buf_flush_lists(PCT_IO(100),LSN_MAX,&n_flushed);//io能力刷新到那个lsn以及传出刷新的块数量//PCT_IO是一个宏如下:#definePCT_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在redobuffer中的if(prev_lsn==0){//静态变量如果是0则代表是第一次执行本函数/*Firsttimearound.*/prev_lsn=cur_lsn;prev_time=ut_time();//获取当前时间return(0);}if(prev_lsn==cur_lsn){//如果没有redo日志生成return(0);}sum_pages+=last_pages_in;time_tcurr_time=ut_time();doubletime_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生成/*HowmuchLSNwehavegeneratedsincelastcall.*/lsn_rate=static_cast<lsn_t>(static_cast<double>(cur_lsn-prev_lsn)/time_elapsed);//计算redolsn生成率lsn_avg_rate=(lsn_avg_rate+lsn_rate)/2;//计算redo每秒平均生成率/*aggregatestatsofallslots*/mutex_enter(&page_cleaner->mutex);ulintflush_tm=page_cleaner->flush_time;ulintflush_pass=page_cleaner->flush_pass;page_cleaner->flush_time=0;page_cleaner->flush_pass=0;ulintlist_tm=0;ulintlist_pass=0;for(ulinti=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();//获取flushlist中最老的lsut_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);//取他们的大值/*Estimatepagestobeflushedforthelsnprogress*///计算target_lsnulintsum_pages_for_lsn=0;lsn_ttarget_lsn=oldest_lsn+lsn_avg_rate*buf_flush_lsn_scan_factor;//计算下一次刷新的目标lsn及target_lsnbuf_flush_lsn_scan_factor是定值3for(ulinti=0;i<srv_buf_pool_instances;i++){//循环整个bufferinstance找到小于target_lsn的脏块buf_pool_t*buf_pool=buf_pool_from_array(i);ulintpages_for_lsn=0;buf_flush_list_mutex_enter(buf_pool);for(buf_page_t*b=UT_LIST_GET_LAST(buf_pool->flush_list);//每个innodbbuffer的末尾的flushlist进行扫描,头插法?b!=NULL;b=UT_LIST_GET_PREV(list,b)){if(b->oldest_modification>target_lsn){break;}++pages_for_lsn;//某个innodbbuffer实例中flushlist小于这个targetlsn的page计数}buf_flush_list_mutex_exit(buf_pool);sum_pages_for_lsn+=pages_for_lsn;//这里汇总所有innodbbuffer实例中flushlist小于这个targetlsn的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/*CapthemaximumIOcapacitythatwearegoingtousebymax_io_capacity.Limitthevaluetoavoidtooquickincrease*/n_pages=PCT_IO(pct_total);//根据前面得到的pct_total和srv_io_capacity参数得到刷新的块数!!!第二个计算参数生成。if(age<log_get_max_modified_age_async()){//如果日质量小于异步刷新的范畴ulintpages_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、根据targetlsn计算出来的一个需要刷新的块数}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函数
doubledirty_pct=buf_get_modified_ratio_pct();//得到修改的块/总的块的的百分比记住脏数据比率if(dirty_pct==0.0){/*Nopagesmodified*/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没有设置/*Theuserhasnotsettheoptiontopreflushdirtypagesasweapproachthehighwatermark.*/if(dirty_pct>=srv_max_buf_pool_modified_pct){//如果脏数据比率大于了innodb_max_dirty_pages_pct则返回比率100%/*WehavecrossedthehighwatermarkofdirtypagesInthiscasewestartflushingat100%ofinnodb_io_capacity.*/return(100);}}elseif(dirty_pct>=srv_max_dirty_pages_pct_lwm){//如果设置了innodb_max_dirty_pages_pct_lwm并且脏数据比率大于了/*Weshouldstartflushingpagesgradually.*///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_taf_lwm=(srv_adaptive_flushing_lwm*log_get_capacity())/100;//srv_adaptive_flushing_lwm=10那么大约就是logtotalsize*(9/10)*(1/10)943349计算一个lowwatermarkif(age<af_lwm){//如果当前生成的redo小于了lowwatermaster则返回0也就是说redo日志量生成量不高则不需要权衡/*Noadaptiveflushing.*///可以看出这里和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没有开启/*Wehavestillnotreachedthemax_asyncpointandtheuserhasdisabledadaptiveflushing.*/return(0);}/*Ifweareherethenweknowthateither:1)Userhasenabledadaptiveflushing2)Usermayhavedisabledadaptiveflushingbutwehavereachedmax_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){caseSRV_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));//430caseSRV_CLEANER_LSN_AGE_FACTOR_HIGH_CHECKPOINT://innodb_cleaner_lsn_age_factor参数默认设置为high_checkpointreturn(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线程分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。