一個double write buffer 有2mb, 共128個page,在mysql 5.6中, 預設有120個page用于批量重新整理(如 lru flush 或者flush list flush),剩下的8個page用于單個page的flush。
在debug版本下,120是可以通過參數innodb_doublewrite_batch_size來配置的,好吧。我已經不安分的把debug宏給去掉了。
全局對象buf_dblwr, 對應結構體為buf_dblwr_t:
ib_mutex_t
mutex
互斥量
ulint
block1
第一個doubewrite 塊(64個page)的page no
block2
第二個double write 塊的page no
first_free
在write_buf中第一個空閑的位置
s_reserved
為單個page重新整理預留的slot數,當flush類型為buf_flush_single_page時,會進入到函數buf_dblwr_write_single_page來寫一個page
b_reserved
為batch flush 預留的slot數
ibool*
in_use
用于标記一個slot是否被使用,隻用于single page flush
ibool
batch_running
當設定為true時,表明有一次batch flush正在進行
byte*
write_buf
double write buffer在記憶體的緩存,以univ_page_size對其
write_buf_unaligned
未對齊的write_buf
buf_page_t**
buf_block_arr
存儲已經cache到write_buf的block的指針
這裡解釋一下flush操作的類型,總的來說,有三種重新整理類型
buf_flush_lru:表示從buffer pool的lru上掃描并重新整理
buf_flush_list:表示從buffer pool的flush list上掃描并重新整理
buf_flush_single_page:從lru上隻重新整理一個page
前兩種屬于batch flush, 最後一種屬于single flush
buf_flush_single_page在幾種情況下使用到:
1.buf_flush_or_remove_page
2.buf_flush_single_page_from_lru,這在free list不夠用時,io-bound場景下,可能頻繁調用到(buf_lru_get_free_block)
3.buf_flush_page_try
todo:下一篇部落格再介紹這幾種刷page的類型
quoted code in buf_flush_write_block_low
938 if (!srv_use_doublewrite_buf || !buf_dblwr) {
939 fil_io(os_file_write | os_aio_simulated_wake_later,
940 false, buf_page_get_space(bpage), zip_size,
941 buf_page_get_page_no(bpage), 0,
942 zip_size ? zip_size : univ_page_size,
943 frame, bpage);
944 } else if (flush_type == buf_flush_single_page) {
945 buf_dblwr_write_single_page(bpage);
946 } else {
947 buf_dblwr_add_to_batch(bpage);
948 }
這裡根據flush的類型來判斷對應的邏輯,如果重新整理類型為buf_flush_single_page,則調用buf_dblwr_write_single_page,否則調用buf_dblwr_add_to_batch
a.
buf_dblwr_write_single_page(page):用于将一個page加入到double write buffer中,并完成寫操作
主要流程如下:
1. 最大為單個page重新整理預留的dblwr page數為: 2 * trx_sys_doublewrite_block_size-srv_doublewrite_batch_size 預設為8個page 當s_reserved值等于最大page數量時,線程會去sleep後retry 2.從srv_doublewrite_batch_size開始,找到一個buf_dblwr->in_use[i] 不為true的slot。配置設定給目前page 将in_use[i]設為true,并遞增s_reserved++ 3.将單個page寫入到double write buffer中。注意,這裡已經釋放了buf_dblwr->mutex 4.刷double write buffer (fil_flush(trx_sys_space)),然後再将資料寫入到磁盤中(buf_dblwr_write_block_to_datafile,buf_flush_sync_datafiles) 注意在函數buf_flush_sync_datafiles中會喚醒io線程,等待寫入完成,是以這是同步寫操作 5.再次持有buf_dblwr->mutex,将s_reserved–, in_use[i], buf_block_arr[i]置為null。
b.
buf_dblwr_add_to_batch(bpage):将一個page加入到double write buffer中,如果batch滿了,則刷double write buffer到磁盤
該函數的邏輯如下:
1.持有buf_dblwr->mutex 2.如果batch_running為true,表示已經有人在開始做batch flush來刷dblwr,釋放互斥鎖,sleep一段時間後,回到1retry 3.如果batch滿了(即buf_dblwr->first_free == srv_doublewrite_batch_size),則釋放mutex, 主動調用buf_dblwr_flush_buffered_writes()把dblwr的寫到磁盤,然後回到1 retry 4. 将page拷貝到第buf_dblwr->first_free個槽位,并設定: buf_dblwr->buf_block_arr[buf_dblwr->first_free] = page; buf_dblwr->first_free++; buf_dblwr->b_reserved++; 5.再次檢查batch flush的槽位是否已滿(buf_dblwr->first_free == srv_doublewrite_batch_size) ,如果滿了,則釋放mutex,進行一次flush(buf_dblwr_flush_buffered_writes()),從函數傳回 6.最後釋放mutex.
c.
buf_dblwr_flush_buffered_writes() : 批量刷double write buffer的函數(隻涉及batch flush的page)
流程如下:
2.如果fist_free為0,表示目前double write buffer為空,釋放mutex,傳回 3.如果batch_running為true,表示正有線程在做batch flush,釋放mutex, sleep一段時間後回到1 retry 4.将batch_running設定為true,避免并發寫 5.釋放buf_dblwr->mutex 6.檢查每一個将要寫dblwr的block以及write_buf中的page是否被損壞或者lsn值是否正确 buf_dblwr_check_block(block) 以及 buf_dblwr_check_page_lsn(write_buf + len2) 7.将write_buf中的page寫入到檔案中 先寫第一個block(<=64個page),再寫第二個block(<=64個page), 也就是說最多一次寫入1m, 分兩次寫的原因是?? 8.将double write buffer刷到磁盤後(fil_flush(trx_sys_space);),逐個開始寫資料檔案(os_aio_simulated_wake_later) 866 buf_dblwr_write_block_to_datafile( 867 buf_dblwr->buf_block_arr[i]); 9.喚醒io線程(os_aio_simulated_wake_handler_threads)
注意這裡,在函數結束時并沒有将batch_running設定為false,因為這裡對資料檔案做的是異步寫,設定标記位的工作留給了io線程來完成
io_handler_thread-> fil_aio_wait-> buf_page_io_complete->buf_flush_write_complete->buf_dblwr_update():
看起來在double write buffer中,batch flush 和singer page flush對應的slot非常獨立,那麼我們是否可以把這個mutex拆成兩個呢?
簡單的測試了一把,結果是。。。。。差别不大
我把自己的想法提到buglist上了,不知道官方的怎麼看。。。