日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
BUG解析:InnoDB兩次寫(xiě)與多實(shí)例bufferpool

在我測(cè)試過(guò)程中,使用的是自動(dòng)提交,一條語(yǔ)句為一個(gè)事務(wù),開(kāi)8個(gè)線程的話大概是單線程復(fù)制的5倍(共有20個(gè)表),性能應(yīng)該還是不錯(cuò)的,多線程下QPS可以達(dá)到32000,單線程差不多6500,但是這是把double write關(guān)了的情況,如果打開(kāi)了double write,那么一開(kāi)始的QPS也差不多是32000,但做幾分鐘之后,這個(gè)數(shù)字一直在減小,那個(gè)感覺(jué)啊真是不好,怎么老是一直減少呢,等到跟上來(lái)了,一直看著它減少到15000,這個(gè)很不好,相當(dāng)于是2倍的提升,這個(gè)看上去完全是因?yàn)閐ouble write的影響,因?yàn)橹皇切薷牧诉@么一個(gè)參數(shù)而出現(xiàn)的兩個(gè)不同的結(jié)果,但是查遍了網(wǎng)上也都說(shuō)double write的影響只會(huì)是5-10%,那么就奇怪了,我這個(gè)的影響明顯是50%以上啊,難道是兩次寫(xiě)就是50%?不對(duì)的,因?yàn)閐ouble write本來(lái)就是連續(xù)寫(xiě)的??隙ㄊ悄睦镉衅渌膯?wèn)題。

超過(guò)十年行業(yè)經(jīng)驗(yàn),技術(shù)領(lǐng)先,服務(wù)至上的經(jīng)營(yíng)模式,全靠網(wǎng)絡(luò)和口碑獲得客戶,為自己降低成本,也就是為客戶降低成本。到目前業(yè)務(wù)范圍包括了:成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì),成都網(wǎng)站推廣,成都網(wǎng)站優(yōu)化,整體網(wǎng)絡(luò)托管,成都小程序開(kāi)發(fā),微信開(kāi)發(fā),App定制開(kāi)發(fā),同時(shí)也可以讓客戶的網(wǎng)站和網(wǎng)絡(luò)營(yíng)銷(xiāo)和我們一樣獲得訂單和生意!

然后在無(wú)奈之下,在測(cè)試時(shí),通過(guò)pstack工具看MYSQL運(yùn)行時(shí)慢到底是什么樣的堆棧,到底是在等啥?什么影響了它的性能,然后看到很多時(shí)候堆棧都是這樣的:

 
 
 
  1. Thread 4 (Thread 0x7fdadd357700 (LWP 9800)): 
  2. #1  0x00000000008d3007 in os_event_wait_low () 
  3. #2  0x00000000008230ae in sync_array_wait_event () 
  4. #3  0x0000000000823f46 in mutex_spin_wait () 
  5. #4  0x00000000008674df in buf_flush_buffered_writes () 
  6. #5  0x0000000000868b97 in buf_flush_batch () 
  7. #6  0x000000000086a6df in buf_flush_list () 
  8. #7  0x00000000008c31b2 in log_check_margins () 
  9. #8  0x00000000008eba6a in row_ins_index_entry_low () 
  10. #9  0x00000000008efd9e in row_ins_step () 
  11. #10 0x0000000000803be9 in row_insert_for_mysql () 
  12. #11 0x00000000007f2d6c in ha_innobase::write_row(unsigned char*) () 
  13. #12 0x000000000068c760 in handler::ha_write_row(unsigned char*) () 
  14. #13 0x000000000055a2ed in write_record(THD*, TABLE*, st_copy_info*) () 
  15. Thread 3 (Thread 0x7fdadd316700 (LWP 9801)): 
  16. #1  0x00000000008d3007 in os_event_wait_low () 
  17. #2  0x00000000008230ae in sync_array_wait_event () 
  18. #3  0x0000000000823f46 in mutex_spin_wait () 
  19. #4  0x00000000008674df in buf_flush_buffered_writes () 
  20. #5  0x0000000000868b97 in buf_flush_batch () 
  21. #6  0x000000000086a6df in buf_flush_list () 
  22. #7  0x00000000008c31b2 in log_check_margins () 
  23. #8  0x00000000008eba6a in row_ins_index_entry_low () 
  24. #9  0x00000000008efd9e in row_ins_step () 
  25. #10 0x0000000000803be9 in row_insert_for_mysql () 
  26. #11 0x00000000007f2d6c in ha_innobase::write_row(unsigned char*) () 
  27. #12 0x000000000068c760 in handler::ha_write_row(unsigned char*) () 
  28. #13 0x000000000055a2ed in write_record(THD*, TABLE*, st_copy_info*) () 

從上面的堆棧中可以看出,SQL線程很多都是在buf_flush_buffered_writes函數(shù)中等待,而這個(gè)函數(shù)正好是處理double write的函數(shù),所以我重點(diǎn)看了這里,然后一進(jìn)去就明白了是為什么了,看到這個(gè)函數(shù)一開(kāi)始有一行mutex_enter(&(trx_doublewrite->mutex)),而在函數(shù)退出前有一行mutex_exit(&(trx_doublewrite->mutex)),里面是處理所有double write緩存起來(lái)的頁(yè)面,也就是前面要刷的頁(yè)面,因?yàn)镮NNODB支持多個(gè)BUFFER POOL實(shí)例,這樣可以增大并發(fā)度,頁(yè)面可以放在不同的BUFFER POOL中,這樣兩個(gè)BUFFER POOL中的頁(yè)面在同時(shí)訪問(wèn)時(shí)可以互不干擾,那么可想而知,double write緩存的頁(yè)面就是來(lái)自多個(gè)SQL線程并發(fā)收集起來(lái)的,那么很容易想到,問(wèn)題其實(shí)就里在這里,由多個(gè)線程做檢查點(diǎn),但只有一個(gè)線程會(huì)做double write,這樣產(chǎn)生了瓶頸,導(dǎo)致等待一段時(shí)間后就會(huì)越來(lái)越慢,也許就是這個(gè)問(wèn)題,那后面就看了一下代碼,它的實(shí)現(xiàn)是否允許多個(gè)線程做檢查點(diǎn)呢,主要是看函數(shù)log_free_check(log_check_margins)的實(shí)現(xiàn),因?yàn)檫@個(gè)函數(shù)才是用戶線程調(diào)用的,代碼是這樣的:

 
 
 
  1.  log_free_check(void) 
  2.        if (log_sys->check_flush_or_checkpoint) 
  3.               log_check_margins(); 

那就主要是log_sys->check_flush_or_checkpoint有沒(méi)有可能多個(gè)線程進(jìn)來(lái)了,最后發(fā)現(xiàn)在里面直接就調(diào)log_checkpoint_margin函數(shù)了,而再進(jìn)去里面,就是對(duì)buffer pool中的臟頁(yè)面進(jìn)行刷盤(pán)了,同時(shí)這里刷盤(pán)是刷每一個(gè)buffer pool instance的,而不是分開(kāi)自己刷自己的,當(dāng)然對(duì)于某一個(gè)buffer pool instance,只會(huì)有一個(gè)線程做,進(jìn)來(lái)之后會(huì)找到?jīng)]有任何一個(gè)線程在做刷盤(pán)的buffer pool instance來(lái)做,所以其實(shí)是并發(fā)處理這多個(gè)buffer pool instance的,那么現(xiàn)在得到的結(jié)論就是經(jīng)常性的多個(gè)線程一起做刷盤(pán)操作,而做完刷盤(pán)之后,如果打開(kāi)了double write,則要將所有的buffer pool instance要刷的頁(yè)面做double write,上面也看到了,它是一個(gè)mutex,多個(gè)線程一起搶這一個(gè)臨界區(qū),導(dǎo)致系統(tǒng)的并發(fā)度大大的降低,那么現(xiàn)在問(wèn)題已經(jīng)很明顯,原因也已經(jīng)很明顯,這個(gè)其實(shí)與DOUBLEWRITE沒(méi)關(guān)系,那個(gè)5-10%我還是承認(rèn)的,這里只不過(guò)是代碼實(shí)現(xiàn)有問(wèn)題而已。

那么結(jié)論就里說(shuō),這其實(shí)是INNODB的一個(gè)BUG,就是多BUFFERPOOL實(shí)例下,DOUBLEWRITE會(huì)導(dǎo)致系統(tǒng)并發(fā)性能大大降低的問(wèn)題。

那如何解決呢?

首先我已經(jīng)向bugs.mysql.com報(bào)了BUG,鏈接http://bugs.mysql.com/bug.php?id=67808&edit=2,本人英語(yǔ)不好,寫(xiě)得挺費(fèi)勁。

難道就這樣等它解決嗎?不對(duì),我已經(jīng)等不上了,即使出來(lái)了也不是在5.5.27上啊,所以自己解決吧。

這里歸根結(jié)底的問(wèn)題就是做檢查點(diǎn)函數(shù)log_checkpoint_margin中存在并發(fā),導(dǎo)致DOUBLEWRITE的瓶頸出現(xiàn)了,因?yàn)樵贗NNODB的增刪改操作的一開(kāi)始,都會(huì)直接先調(diào)用log_free_check這個(gè)函數(shù),出現(xiàn)這樣的問(wèn)題的概率太高了。

想想,這個(gè)做檢查點(diǎn)需要多個(gè)線程嗎?如果是一個(gè)線程在做是不是就沒(méi)有問(wèn)題了?DOUBLEWRITE的瓶頸也不存在了?確實(shí)是這樣的。

再想想,做檢查點(diǎn)需要多個(gè)線程嗎?只有一個(gè)線程做是不是就夠了?因?yàn)闄z查點(diǎn)歸根結(jié)底是為了給日志讓空間出來(lái),日志一直往2個(gè)(默認(rèn))日志文件中循環(huán)添加,第一個(gè)寫(xiě)完寫(xiě)第二個(gè),寫(xiě)完第二個(gè)再寫(xiě)第一個(gè),其實(shí)就里一個(gè)圈,不斷的循環(huán),那么這里就必須要保證,向里面寫(xiě)的數(shù)據(jù)的位置不能走到檢查點(diǎn)的位置的前面去(因?yàn)閿?shù)據(jù)的LSN是新產(chǎn)生的日志的LSN,肯定是要小于檢查點(diǎn)的LSN的,也可以表示為,數(shù)據(jù)的LSN必須要小于檢查點(diǎn)的LSN加上整個(gè)日志組的日志容量),因?yàn)闄z查點(diǎn)LSN前面的日志表明,所有數(shù)據(jù)已經(jīng)都寫(xiě)入磁盤(pán)了,可以扔掉了,那如果大于了,就會(huì)把沒(méi)有做檢查點(diǎn)的日志覆蓋掉,這樣會(huì)導(dǎo)致數(shù)據(jù)錯(cuò)誤或者更嚴(yán)重的一些問(wèn)題。

有了這樣的想法,則這個(gè)問(wèn)題應(yīng)該不難解決,先在log_sys中加入一個(gè)成員checkpoint_doing,用來(lái)表示現(xiàn)在是否有線程正在做檢查點(diǎn),再修改函數(shù)log_check_margins,最前面加上代碼段:

 
 
 
  1. mutex_enter(&(log_sys->mutex)); 
  2. if (log_sys->checkpoint_doing > 0) { 
  3.        mutex_exit(&(log_sys->mutex)); 
  4.       return; 
  5. log_sys->checkpoint_doing++; 
  6. mutex_exit(&(log_sys->mutex)); 

上面這表示如果有線程已經(jīng)做了,那這里不會(huì)再進(jìn)去,直接就出去了,如果沒(méi)有線程在做,那么當(dāng)前線程才做,同時(shí)將標(biāo)志置為正在做。這樣保證了只有一個(gè)用戶線程會(huì)做檢查點(diǎn)。當(dāng)然在修改及判斷這個(gè)checkpoint_doing的時(shí)候必須要對(duì)其進(jìn)行保護(hù),上面代碼中也已經(jīng)有所體現(xiàn)。那么這樣就好了嗎?如果當(dāng)前系統(tǒng)的壓力非常大,那么出去了,而沒(méi)有做檢查點(diǎn)檢查,繼續(xù)做寫(xiě)操作,這樣有可能會(huì)導(dǎo)致新的日志寫(xiě)的超過(guò)了檢查點(diǎn)的位置,導(dǎo)致數(shù)據(jù)覆蓋,所以還需要做一個(gè)修改操作。

因?yàn)樵贗NNODB中寫(xiě)日志的函數(shù)只有l(wèi)og_write_up_to,并且這只會(huì)有一個(gè)線程寫(xiě),那么為了防止這個(gè)問(wèn)題的話是不是在它寫(xiě)日志的時(shí)候檢查一下,如果空間不夠了等待或者做一次檢查點(diǎn)后再繼續(xù)做,是不是就沒(méi)有問(wèn)題了?我認(rèn)為確實(shí)是這樣的,那么繼續(xù)修改:

 
 
 
  1. if (!log_sys->checkpoint_waiting && log_sys->lsn - log_sys->last_checkpoint_lsn > log_sys->max_checkpoint_age) 
  2.      mutex_exit(&(log_sys->mutex)); 
  3.        log_sys->checkpoint_waiting = 1; 
  4.        log_check_margins(); 
  5.        log_sys->checkpoint_waiting = 0; 
  6.        goto loop; 

這段代碼就加在log_write_up_to函數(shù)中五個(gè)判斷條件之后,能走到這里說(shuō)明這次的日志要寫(xiě)入日志文件了,那么這里檢查是最合適的,上面的代碼有一個(gè)條件判斷,最主要是的log_sys->lsn - log_sys->last_checkpoint_lsn > log_sys->max_checkpoint_age,這個(gè)表示的是如果當(dāng)前的最新LSN超過(guò)檢查點(diǎn)LSN的數(shù)目已經(jīng)大于最大的做檢查點(diǎn)差值數(shù),則就等待或者做一次檢查點(diǎn),這個(gè)條件與log_checkpoint_margin函數(shù)中判斷是不是要做檢查點(diǎn)的條件是一樣的,這樣的話就保證了這段代碼中調(diào)用了log_check_margins時(shí)要么里面已經(jīng)有人正在做,要么自己肯定能做一次檢查點(diǎn),不然在這里會(huì)產(chǎn)生死循環(huán)。做了之后從而使的log_sys->last_checkpoint_lsn變大,向前走,讓出空間,這樣這次日志就可以寫(xiě)入進(jìn)去了,那么goto loop可以起到循環(huán)等待的作用。

上面還看到一個(gè)新的成員checkpoint_waiting,這個(gè)是為了防止進(jìn)入死循環(huán)而設(shè)置的,因?yàn)閘og_check_margins里面還會(huì)再調(diào)用log_write_up_to。

那么到現(xiàn)在為止,這個(gè)問(wèn)題應(yīng)該算是可以了的,接下來(lái)就是測(cè)試了,把多線程的SLAVE復(fù)制跑起來(lái),我發(fā)現(xiàn)這個(gè)是一個(gè)非常好的并發(fā)測(cè)試工具,不需要專門(mén)寫(xiě)應(yīng)用來(lái)設(shè)置并發(fā)環(huán)境。

測(cè)試的結(jié)果表明,那么問(wèn)題不復(fù)存在,平均的QPS在打開(kāi)DOUBLEWRITE時(shí)都是31000,這個(gè)數(shù)字挺好的。問(wèn)題解決,同時(shí)發(fā)現(xiàn)那個(gè)分支就從來(lái)沒(méi)有進(jìn)去過(guò),說(shuō)明用戶線程做了已經(jīng)足夠了,那里只是一個(gè)機(jī)率很小的問(wèn)題預(yù)防而已。

但是這個(gè)修改現(xiàn)在還沒(méi)有辦法去驗(yàn)證,只能由各位先從理論上看看是不是正確吧,我本人認(rèn)為應(yīng)該還是沒(méi)什么大問(wèn)題的,請(qǐng)各位大俠指點(diǎn)!

這里要感謝一下我的好朋友好戰(zhàn)友陳福榮同學(xué),在MYSQL學(xué)習(xí)及實(shí)現(xiàn)方面一直不斷的討論,研究,我們共同進(jìn)步。

原文鏈接:http://www.cnblogs.com/bamboos/archive/2012/12/05/2802997.html

【編輯推薦】

  1. 適合初學(xué)者的MySQL學(xué)習(xí)筆記之庫(kù)操作示例
  2. 適合初學(xué)者的MySQL學(xué)習(xí)筆記之表操作示例
  3. 適合初學(xué)者的MySQL學(xué)習(xí)筆記之MySQL管理心得
  4. 適合初學(xué)者的MySQL學(xué)習(xí)筆記之MySQL查詢示例
  5. 適合初學(xué)者的MySQL學(xué)習(xí)筆記之管理員常用操作總結(jié)

網(wǎng)站標(biāo)題:BUG解析:InnoDB兩次寫(xiě)與多實(shí)例bufferpool
本文鏈接:http://www.dlmjj.cn/article/dphsids.html