MySQL:刷髒相關
來源:MySQL學習
這裡簡單記錄一下刷髒和IO程式碼的簡要流程(8.0.23),以下內容僅供參考。先簡要總結一下:
當大於預設8192(innodb_lru_scan_depth*innodb_buffer_pool_instances)的時候只有flush list刷髒,也就是buffer pool 的page會盡量填充,不做LRU淘汰。 當小於預設8192(innodb_lru_scan_depth*innodb_buffer_pool_instances)的時候會觸發LRU掃描,掃描的時候需要釋放page,如果page是乾淨的則直接釋放放入free list,且做LRU淘汰。如果page不是乾淨的則需要進行LRU刷髒,完成後且放入free list,且需要做LRU淘汰。 刷盤總是先寫入到DBLWR,這是同步IO的方式,如果不是O_D開啟的檔案還需要做對DBLWR做FSYNC。 刷盤寫入資料檔案由DBLWR部分完成,為非同步IO,非同步IO主要為前臺thread 發起io_submit,後臺非同步IO執行緒堵塞等待結果,然後處理完成的一些動作,比如檢查page的IO狀態,資料檔案的fsync操作,刷盤後一個更加重要的動作是非同步IO執行緒還要完成記憶體的維護,比如對於flush list刷髒需要從flush list連結串列中刪除,對於lru刷髒則額外還需要從page的hash結構和LRU連結串列中刪除並且返回給free list成為可用的空閒page。 非同步IO通常在Linux上為Linux native aio,但是需要滿足條件innodb_use_native_aio = ON、libaio安裝、innodb_flush_method =O_DIRECT 讀取為同步IO,預讀為非同步IO,刷髒為非同步IO,寫DBLWR為同步IO。 DBLWR的預設情況下為2個物理檔案,其中一個要大一些,固定為512*16K,為s_single_segments型別的segment。
一、刷髒
當cleaner執行緒及其worker執行緒需要刷髒的時候,先計算需要刷髒的page數量,然後呼叫pc_flush_slot進行刷髒,我們就從這個函式開始向下探索。
下面是一些主要流程供參考,
pc_flush_slot
獲取一個instance進行刷盤
-----------------LRU 淘汰如果是髒頁則需要刷髒,淘汰中還要判斷是否為state 頁
->buf_flush_LRU_list
首先進行LRU刷盤,!!!主要是如果free_len 小於了srv_LRU_scan_depth需要開啟LRU淘汰或者刷盤
->buf_flush_do_batch(buf_pool, BUF_FLUSH_LRU, scan_depth, 0, &n_flushed);
標記為LRU刷盤,帶入掃描深度,返回刷盤的page數量 srv_LRU_scan_depth
->buf_flush_batch(buf_pool, type, min_n, lsn_limit)
min_n為帶入的scan_depth引數
->buf_flush_start
->buf_pool->init_flush[flush_type] = TRUE
正在進行BUF_FLUSH_LRU刷髒
-> switch (flush_type)
case BUF_FLUSH_LRU:(這裡為LRU刷髒)
mutex_enter(&buf_pool->LRU_list_mutex);
->count = buf_do_LRU_batch(buf_pool, min_n)
這裡的count是需要返回的重新整理的page數量
-> buf_flush_LRU_list_batch(buf_pool, max - count)
max - count普通情況就是max
------見下1.1-----這個就比較複雜了,需要判斷是否為state page/乾淨page/髒頁,前面2個自己維護記憶體,髒頁刷盤後非同步IO完成記憶體維護
mutex_exit(&buf_pool->LRU_list_mutex);
->buf_flush_batch
->count = buf_do_LRU_batch(buf_pool, min_n)
呼叫LRU重新整理
->buf_flush_end
-------------------FLUSH LIST刷髒,相對簡單判斷也比較少,因為flush list都是按照順序排列的髒頁,而且不釋放page只負責刷髒,那麼在壓力小的情況LRU 刷髒基本都是乾淨的page
->buf_flush_do_batch(buf_pool, BUF_FLUSH_LIST, slot->n_pages_requested,page_cleaner->lsn_limit, &slot->n_flushed_list)
slot->n_pages_requested 引數和io_cap相關。type為BUF_FLUSH_LIST
->buf_flush_start
-> buf_pool->init_flush[flush_type] = TRUE
設定對應的type正在進行重新整理
-> os_event_reset(buf_pool->no_flush[flush_type])
設定沒有被喚醒
->buf_flush_batch
->switch (flush_type)
case BUF_FLUSH_LIST
這裡帶入的flush list刷髒
->buf_do_flush_list_batch(buf_pool, min_n, lsn_limit)
len = UT_LIST_GET_LEN(buf_pool->flush_list)
獲取flush list的長度
->for (buf_page_t *bpage = UT_LIST_GET_LAST(buf_pool->flush_list);count < min_n && bpage != nullptr
&& len > 0 &&bpage->get_oldest_lsn() < lsn_limit;bpage = buf_pool->flush_hp.get(), ++scanned)
從尾部開始迴圈,結束條件是
A count < min_n:如果重新整理page數量的大於了需要重新整理的page數量
B len > 0:有需要重新整理的page,空閒例項這個條件不會滿足
C bpage->get_oldest_lsn() < lsn_limit:lsn_limit一般比較大
->buf_flush_page_and_try_neighbors(bpage, BUF_FLUSH_LIST, min_n, &count)
這個參考程式碼已經標記了,但是LRU刷髒還會返回給free list
從刷髒後的非同步IO的buf_page_io_complete函式中也可以看出BUF_FLUSH_LIST不需要淘汰LRU
------------------------------------------------------BUF_FLUSH_LIST: don't evict
1.1 buf_flush_LRU_list_batch
buf_flush_LRU_list_batch
->for (bpage = UT_LIST_GET_LAST(buf_pool->LRU);
bpage != nullptr && count + evict_count < max &&
free_len < srv_LRU_scan_depth + withdraw_depth &&
lru_len > BUF_LRU_MIN_LEN;
++scanned, bpage = buf_pool->lru_hp.get())
從LRU的尾部開始掃描page,實際上這裡最重要的2個條件
A: count + evict_count < max
如果重新整理的page數量小於了預期srv_LRU_scan_depth引數指定深度的page數量
這裡evict_count代表stale page直接清理掉
這裡count代表刷髒後清理掉
B: free_len < srv_LRU_scan_depth + withdraw_depth
如果當前instance中free的page數量小於了srv_LRU_scan_depth引數的設定
注意如果這兩個條件不滿足則不會進行LRU刷髒
->if (bpage->was_stale())
如果page是stale page
->buf_page_free_stale(----OK)
->auto *block_mutex = buf_page_get_mutex(bpage)
上page buf_block_t的mutex
->io_type = buf_page_get_io_fix(bpage)
獲取IO型別
->if (io_type == BUF_IO_NONE)
如果io_type為NONE 這代表可以進行處理
->if (bpage->is_dirty())
如果是髒塊
->buf_flush_remove(bpage)
從flush list中刪除,注意stale狀態的page是直接從flush list刪除的,而不需要刷盤
->UT_LIST_REMOVE(buf_pool->flush_list, bpage)
從flush list中刪除
->bpage->set_clean()
set_oldest_lsn(0)
->if (bpage->get_flush_observer() != nullptr)
-> bpage->get_flush_observer()->notify_remove(buf_pool, bpage);
-> bpage->reset_flush_observer();
和DDL 有關
<-buf_flush_remove
->buf_LRU_free_page
從hash結構和LRU中去掉這個page,並且反還給free list
持有鎖A.buf_pool->LRU_list_mutex B.buf_page_get_mutex(bpage)
->buf_page_can_relocate
if (!buf_page_can_relocate(bpage))
->(buf_page_get_io_fix(bpage) == BUF_IO_NONE && bpage->buf_fix_count == 0)
判定是否處於IO fixed狀態,如果處於則return false,不能直接去掉
<-buf_page_can_relocate
-> if (is_dirty)
如果是髒快則return (false),但是對這裡來講肯定不是前面已經判定了,如果是髒頁這設定lsn為0
-> buf_LRU_block_remove_hashed
if (!buf_LRU_block_remove_hashed(bpage, zip, false))
從hash結構和LRU中去掉這個page,這需要持有 A.buf_pool->LRU_list_mutex B.buf_page_get_mutex(bpage) C.rw_lock_own(hash_lock, RW_LOCK_X)
同時需要確保IO沒有被fixed buf_page_get_io_fix(bpage) == BUF_IO_NONE
->buf_LRU_remove_block(bpage)
從LRU連結串列刪除page
->buf_LRU_adjust_hp(buf_pool, bpage);
調整風險指標
->if (bpage == buf_pool->LRU_old)
如果來到3/8 端 需要對指標進行調整
->buf_page_t *prev_bpage = UT_LIST_GET_PREV(LRU, bpage);
獲取前一個page
->buf_page_set_old(prev_bpage, TRUE); buf_pool->LRU_old_len++;
設定為old指標
->UT_LIST_REMOVE(buf_pool->LRU, bpage)
從LRU 刪除這個page
->buf_page_is_old(bpage)
調整old len的長度
->buf_LRU_old_adjust_len(buf_pool)
調整old page的指標
->BUF_BLOCK_FILE_PAGE
->buf_block_modify_clock_inc((buf_block_t *)bpage)
這一步主要作為最佳化,增加了buf_block_t的modify_clock++ 如果遊標中的page本值沒有改變則說明沒有修改過,可以繼續使用,否則?重新定位?
->buf_page_hash_get_low
hashed_bpage = buf_page_hash_get_low(buf_pool, bpage->id);
上hash lock
->HASH_SEARCH(hash, buf_pool->page_hash, page_id.fold(), buf_page_t *, bpage,
ut_ad(bpage->in_page_hash && !bpage->in_zip_hash &&
buf_page_in_file(bpage)),page_id == bpage->id);
進行hash查詢,這裡用到的是bpage->id這是一個space id和page no的結構體
/** Tablespace id. */space_id_t m_space;
/** Page number. */ page_no_t m_page_no;
->返回找到的page,
A.如果找不到,報錯 not found in the hash table B.當前page進行比對是否是同一個page,如果不是則報錯 n hash table we find block of which is not
->HASH_DELETE(buf_page_t, hash, buf_pool->page_hash, bpage->id.fold(), bpage)
從例項的hash結構刪除這個page
-> switch (buf_page_get_state(bpage))
buf_page_set_state(bpage, BUF_BLOCK_REMOVE_HASH)
標記page為從hash中刪除了,這是一個比較短暫的狀態 ----注意page 狀態的轉換
<- buf_LRU_block_remove_hashed
->btr_search_drop_page_hash_index((buf_block_t *)bpage)
AHI 刪除這個PAGE
->buf_LRU_block_free_hashed_page((buf_block_t *)bpage)
-> buf_block_set_state(block, BUF_BLOCK_MEMORY) ----注意page 狀態的轉換
先設定為BUF_BLOCK_MEMORY狀態
->buf_LRU_block_free_non_file_page
->buf_block_set_state(block, BUF_BLOCK_NOT_USED) ----注意page 狀態的轉換
設定page狀態為BUF_BLOCK_NOT_USED
->UT_LIST_ADD_FIRST(buf_pool->free, &block->page)
將page加入到free list
<-buf_LRU_block_free_hashed_page
<-buf_LRU_free_page
<-buf_page_free_stale
->else 如果不是stale page -------- 這裡包含了是否為髒頁,分別走不同的邏輯,如果不是髒頁自己維護記憶體,如果是髒頁非同步IO維護記憶體
->mutex_enter_nowait
buf_block_t的mutex,這裡拿這個鎖,因為下面要進行判斷
->if (acquired && buf_flush_ready_for_replace(bpage))
這個分支主要是走的是非髒頁進行淘汰,不需要刷盤
->buf_flush_ready_for_replace(bpage) 主要判斷是否為髒頁,並且IO沒有被fixed住,判定如下
->if (!buf_page_in_file(bpage))
因為這裡是掃描的LRU,如果有幾種狀態比如BUF_BLOCK_REMOVE_HASH/BUF_BLOCK_MEMORY/BUF_BLOCK_NOT_USED/BUF_BLOCK_READY_FOR_USE
這幾種狀態的page是明顯不應該在LRU上的,因此需要報錯Buffer block " << bpage << " state " << bpage->state << " in the LRU list!";
->if (!buf_page_can_relocate(bpage))
需要判斷page是否可以被重新填充,如果IO fixed了則不能重新填充
-> return(buf_page_get_io_fix(bpage) == BUF_IO_NONE && bpage->buf_fix_count == 0)
需要判斷是否有IO FIXED的情況
->if (bpage->was_stale())
是否處於stale狀態如果是則返回為true(判斷space是否已經drop 過了),在這個流程下前面是判斷過的
->bpage->is_dirty
返回的是 這個page是否是髒頁,很明顯如果是非髒頁,這不需要刷盤
<-buf_flush_ready_for_replace
->如果不是髒頁
->(buf_LRU_free_page(bpage, true))
參考上面 從hash結構和LRU中去掉這個page,並且反還給free list,因為不是髒頁,因此不需要刷盤
++evict_count,不需要刷盤的就是就是這個統計
->else if (acquired && buf_flush_ready_for_flush(bpage, BUF_FLUSH_LRU))
這個分支主要是走的髒頁淘汰,這個需要刷盤的----------------------------- 刷盤後的非同步IO執行緒來完成記憶體的維護
->buf_flush_ready_for_flush
->!bpage->is_dirty()||buf_page_get_io_fix_unlocked(bpage) != BUF_IO_NONE
主要判斷是否io 被fix住了,並且判斷是否是髒頁
->(buf_page_get_state(bpage) != BUF_BLOCK_REMOVE_HASH)
->buf_flush_page_and_try_neighbors(bpage, BUF_FLUSH_LRU, max, &count)
同樣呼叫臨近頁刷髒,flush list也是這樣刷的
->buf_flush_page
呼叫這裡進行刷髒,見下1.2,從後面的IO 非同步刷到磁碟的動作來看在buf_page_io_complete函式中,也是會進行LRU淘汰等管理操作,--------------------------------BUF_FLUSH_LRU: always evict
1.2 buf_flush_page
buf_flush_page
刷髒
先設定了page的IO fix等狀態
->buf_flush_write_block_low
->buf_flush_init_for_writing
初始化物理page的相關header 比較重要
->dblwr::write
先看正常的刷髒邏輯
->page_id = bpage->id
->if (!sync && flush_type != BUF_FLUSH_SINGLE_PAGE)
這裡只有SINGLE_PAGE刷髒才會用到sync=true,正常的LRU和FLUSH LIST刷髒都走這裡
->Double_write::submit(flush_type, bpage, e_block, e_len)
->dblwr = instance(flush_type, bpage)
呼叫Double_write::instance,返回dblwr instance
->Double_write::instance buf0dblwr.cc:508
instance(flush_type, buf_pool_index(buf_pool_from_bpage(bpage)))
獲取page所在buffer instance id,再次呼叫過載的Double_write::instance
->Double_write::instance buf0dblwr.cc:330
實際就是根據flush_type計算出使用哪一個dblwr instance
計算方式主要還是分LRU和FLUSH LIST ,其中BUF_FLUSH_LIST為後面8個
BUF_FLUSH_LRU為前面8個,主要還是根據buffer的instance來匹配用哪個
也就是每個instance使用2個BS,其中一個為BUF_FLUSH_LRU另一個為
BUF_FLUSH_LIST
->dblwr->enqueue(flush_type, bpage, e_block, e_len)
Double_write::enqueue
->Double_write::prepare
這個函式作用不大,主要檢查一下page
->for (;;)
->mutex_enter(&m_mutex)
對本dblwr instance加鎖
->if (m_buffer.append(frame, len))
呼叫dblwr::Buffer::append,這個函式如果正常寫入則返回為true,跳出迴圈,否則buffer空間滿
則返回flase
{ break;}
->if (flush_to_disk(flush_type))
buffer滿刷入到磁碟,呼叫Double_write::flush_to_disk
->Double_write::wait_for_pending_batch
->Double_write::write_pages
->segments->dequeue(batch_segment)
出隊一個Batch_segment,出隊演演算法?
二、DBWRL 系統
s_flush_list_batch_segments(buffer instance=8 innodb_doublewrite_pages=4 srv_n_write_io_threads=4,f0: (4*9)*16K)
BS0 BS1 ... BS8
S0 S1 S2 S3 S0 S1 S2 S3 S0 S1 S2 S3
| | | | | | | | | | | |
| | | | | | | | | | | |
16K 16K 16K 16K 16K 16K 16K 16K ... 16K 16K 16K 16K f0: (4*9)*16K
s_LRU_batch_segments(buffer instance=8 innodb_doublewrite_pages=4 srv_n_write_io_threads=4,f1:(4*9)*16K)
s_single_segments(f1:512*16K)
BS0 BS1 ... BS8 S37 S38 ... S547 S548
S0 S1 S2 S3 S0 S1 S2 S3 S0 S1 S2 S3 | | | |
| | | |
| | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | |
16K 16K 16K 16K 16K 16K 16K 16K ... 16K 16K 16K 16K 16K 16K ... 16K 16K
----------------------(4*9)*16K-------------------------------------- ----------512*16K---------
s_segments flush:BS0...flush:BS8|LRU:BS0......LRU:BS8
s_instances(vector):
Double_write0 Double_write1 ... Double_write14 Double_write15
| | | |
m_buffer(16K*4) m_buffer(16K*4) m_buffer(16K*4) m_buffer(16K*4)
m_buf_pages(verctor(4)) m_buf_pages(verctor(4)) m_buf_pages(verctor(4)) m_buf_pages(verctor(4))
--std::tuple --std::tuple --std::tuple --std::tuple
--std::tuple --std::tuple --std::tuple --std::tuple
--std::tuple --std::tuple --std::tuple --std::tuple
--std::tuple --std::tuple --std::tuple --std::tuple
--------前8 instance為BUF_FLUSH_LIST使用---------- -----------後8個 instance為BUF_FLUSH_LRU---------
2.1 DBLWR segment的建立
dblwr::open
->Double_write::s_n_instances = std::max(4UL, srv_buf_pool_instances * 2)
dblwr的例項個數和buffer pool的instance個數相關,並且每個buffer pool的instance
包含了一個LRU list和一個flush list 兩個dblwr例項,預設為16
->if (dblwr::n_files == 0){dblwr::n_files = 2}
定義全域性變數dblwr::n_files的個數和引數innodb_doublewrite_files有關,預設為2,一般也不會調整這個引數
也就是dblwr物理檔案的個數,從下面的判斷來看最大數量不能大於Double_write::s_n_instances的定義
->if (dblwr::n_pages == 0){dblwr::n_pages = srv_n_write_io_threads}
定義每一個batch segment的page數量,和引數innodb_doublewrite_pages有關,預設為4,也是srv_n_write_io_threads
的大小
這裡使用預設引數innodb_doublewrite_files=2為列子
->Double_write::s_files.resize(dblwr::n_files)
定義s_files vecotr陣列的大小為dblwr::n_files大小,也就是有多少的dblwr檔案
->segments_per_file = (Double_write::s_n_instances / dblwr::n_files) + 1
定義每個檔案batch segment的個數,這裡可以看到預設實際上就是16/2+1 也就是9個segments
這裡定義的batch segment
->dblwr::File::s_n_pages = dblwr::n_pages * segments_per_file
定義每個檔案用於batch segment的pages的總數,預設segments_per_file為9,dblwr::n_pages為4
也就是36
->for (auto &file : Double_write::s_files)
物理部分1
迴圈每個dblwr物理檔案,檔案下標從0開始
->dblwr_file_open(dblwr::dir, &file - first, file, OS_DBLWR_FILE)
建立檔案,其中dblwr::dir為引數innodb_doublewrite_dir指定的位置
其中可以看到建立檔案的規則,比如檔名怎麼來的
->pages_per_file = dblwr::n_pages * segments_per_file
這裡pages_per_file為每個檔案包含的batch segment pages的個數
->if ((file.m_id & 1))
如果檔案的序號為奇數(0,1)
->pages_per_file += SYNC_PAGE_FLUSH_SLOTS / (Double_write::s_files.size() / 2)
那麼pages_per_file還需要加上segment 的page個數,預設為512(SYNC_PAGE_FLUSH_SLOTS)個pages用於single dblwr write
而預設Double_write::s_files.size()就是2,那麼預設的情況下dblwr檔案1就會比檔案0大512個page,這個512個用於
single dblwr write
->Double_write::init_file(file, pages_per_file)
根據計算的pages數量來初始化檔案大小
-----以上檔案初始化完成,但是需要建立相應的記憶體結構
->Double_write::create_batch_segments(segments_per_file)
這裡輸入的是每個檔案batch segment的個數,但是初始化是LRU和FLUSH LIST都要初始化完成的
->n_segments = segments_per_file * s_files.size();
用batch segment的個數*檔案個數,預設為9*2=18
->n = std::max(ulint{2}, ut_2_power_up((n_segments + 1)))
18+1 後取2的N次方,實際上就是32,這裡可以看到記憶體結構實際上多了一些batch segment
->s_LRU_batch_segments = UT_NEW_NOKEY(Batch_segments(n))
->s_flush_list_batch_segments = UT_NEW_NOKEY(Batch_segments(n))
初始化s_LRU_batch_segments和s_flush_list_batch_segments其大小都是32個batch segment
->total_pages = segments_per_file * dblwr::n_pages
計算每個檔案用於batch segment的page數量
-> for (auto &file : s_files)
迴圈每個dblwr檔案
->for (uint32_t i = 0; i < total_pages; i += dblwr::n_pages, ++id)
這裡以dblwr::n_pages也就是每個batch segment的pages個數作為增加值來初始化
每個batch segment
->auto s = UT_NEW_NOKEY(Batch_segment(id, file, i, dblwr::n_pages))
建立batch segment,並且進行標號,從0開始標號
->segments = (file.m_id & 1) ? s_LRU_batch_segments: s_flush_list_batch_segments;
如果dblwr檔案為奇數這為s_LRU_batch_segments,偶數為s_flush_list_batch_segments
f0:s_flush_list_batch_segments
f1:s_LRU_batch_segments
->success = segments->enqueue(s)
將初始化的batch segment放入到相應的佇列中
->s_segments.push_back(s)
並且放入s_segments陣列中
->Double_write::create_single_segments(segments_per_file)
物理部分2
->n_segments = std::max(ulint{2}, ut_2_power_up(SYNC_PAGE_FLUSH_SLOTS))
因為SYNC_PAGE_FLUSH_SLOTS為512為2的n次方
->s_single_segments = UT_NEW_NOKEY(Segments(n_segments))
分配對應數量的segment,作為single dblwr的segment
->n_pages = SYNC_PAGE_FLUSH_SLOTS / (s_files.size() / 2)
預設s_files=2,因此這裡n_pages=512
->for (auto &file : s_files)
開始迴圈每個檔案
->if (!(file.m_id & 1) && s_files.size() > 1){continue;}
偶數檔案直接continue,因此這裡是奇數檔案 奇數檔案就是s_LRU_batch_segments 對應的檔案,那麼這個檔案要大一些
->start = dblwr::File::s_n_pages
這裡跳過batch segments部分
->for (uint32_t i = start; i < start + n_pages; ++i)
這裡start為batch segments部分,n_pages為single dblwr的segment的page數量,每次增加1
->auto s = UT_NEW_NOKEY(Segment(file, i, 1UL));
這裡的1UL就代表一個page,根據資訊初始化segment資訊
->success = s_single_segments->enqueue(s)
加入到s_single_segments中這是一個Segments的陣列
->Double_write::create_v2()
建立記憶體部分資料結構
->for (uint32_t i = 0; i < s_n_instances; ++i)
根據s_n_instances的數量進行dblwr instance的初始化,s_n_instances就是buffer instance *2 為16
->ptr = UT_NEW_NOKEY(Double_write(i, dblwr::n_pages))
其中dblwr::n_pages為batch segment對應的page數量,預設為4
->auto ptr = UT_NEW_NOKEY(Double_write(i, dblwr::n_pages))
m_id(id), m_buffer(n_pages), m_buf_pages(n_pages)
mutex_create(LATCH_ID_DBLWR, &m_mutex)
這裡主要初始化一個mutex和響應的buffer,初始化m_buffer和m_buf_pages
A.m_buffer:定義為dblwr::Buffer型別,其本質為一段記憶體,其大小為n_pages+1,考慮到對齊操作因此+1,這是作為快取用的
B.m_buf_pages:定義為Double_write::Buf_pages型別,其為一個std::tuple<buf_page_t *, const file::Block *, uint32_t>
型別的vector,verctor的大小為n_pages定義,這是收集的髒資料的buf_page_t
->s_instances->push_back(ptr)
將初始化好的Double_write的指標push到s_instances中
2.2 DBLWR segment 的使用
Double_write::flush_to_disk
如果本instance的buffer滿了則寫入到磁碟
->wait_for_pending_batch()
等待其他batch write 寫入
-> segments = flush_type == BUF_FLUSH_LRU ? s_LRU_batch_segments : s_flush_list_batch_segments;
根據型別分配到底是那個BS,因為一個instance分為2個BS,一個是LRU的一個是FLUSH的
-> segments->dequeue(batch_segment)
出隊已滿的BS,因為一個BS,就是4個page的物理空間。這裡需要確認如何選擇BS的,debug來看都是整數
!!!!!在某些情況下不一定buffer滿才刷page,因此可能存在寫 1 2 3 個page的情況
-> batch_segment->start(this)
呼叫Batch_segment::start,傳入本instance,標記已經開始刷盤
-> batch_segment->write(m_buffer)
呼叫Batch_segment::write,將instance中buffer的資料寫入到本batch_segment
->Segment::write(buffer.begin(), buffer.size())
->IORequest req(IORequest::WRITE | IORequest::DO_NOT_WAKE)
建立io請求
->req.dblwr()
m_type |= DBLWR
設定type為DBLWR
->os_file_write_retry(req, m_file.m_name.c_str(), m_file.m_pfs,ptr, m_start, len)
len 也可以是一個page,force的情況,也可以是4個page或者1個page,
->pfs_os_file_write_func
->os_file_write_retry
->os_file_pwrite(同步IO)
-> m_buffer.clear()
清理buffer instance的快取
->if (is_fsync_required())
呼叫Double_write::is_fsync_required
->srv_unix_file_flush_method != SRV_UNIX_O_DIRECT && srv_unix_file_flush_method != SRV_UNIX_O_DIRECT_NO_FSYNC
是否需要刷盤,如果為O_DIRECT 就不需要刷DBLWR,如果需要刷盤則呼叫下面的
-> batch_segment->flush()
Segment::flush
->os_file_flush(m_file.m_pfs)
->for (uint32_t i = 0; i < m_buf_pages.size(); ++i)
開啟迴圈
->bpage = std::get<0>(m_buf_pages.m_pages[i])
->bpage->set_dblwr_batch_id(batch_segment->id())
->write_to_datafile(bpage, false, std::get<1>(m_buf_pages.m_pages[i]),std::get<2>(m_buf_pages.m_pages[i]))
髒資料寫入到資料檔案,page\false\壓縮相關\長度
Double_write::write_to_datafile
這裡不考慮壓縮的page也就是e_block == nullptr的情況
->Double_write::prepare(in_bpage, &frame, &len)
做一些檢查
->IORequest io_request(type)
建立IO請求
IORequest::WRITE
不需要做fsync
IORequest::DO_NOT_WAKE
->fil_io(io_request, sync, bpage->id, bpage->size, 0, len, frame, bpage)
->shard = fil_system->shard_by_id(page_id.space())
獲取shard
->shard->do_io((type, sync, page_id, page_size, byte_offset, len, buf,message)
Fil_shard::do_io
三、非同步IO部分
預讀為例子:
-----------共享io_context_t--------------
| |
| |
session thread kernel AIO thread
| | |
| | |
| | |
|---------> | |
| 提交IO | |
| IO_SUBMIT 處理IO請求 |
| | <-----------------|
提交後session io_getevents |
就可以繼續了不 收割IO |
等待IO完成 |
Fil_shard::complete_io
處理檔案的相關資訊
buf_page_io_complete
處理page的資訊
非同步IO主要完成不是馬上需要page的情況,可以將page的讀取
由AIO thread 處理後直接調入到記憶體,比如預讀。
3.1 刷髒提交IO
Fil_shard::do_io
->aio_mode = get_AIO_mode(req_type, sync)
獲取aio的型別,這裡一共有幾種IO型別,
AIO_mode::SYNC 同步 IO
AIO_mode::LOG redo IO
AIO_mode::NORMAL 普通 非同步IO
AIO_mode::IBUF ibuf IO
if (sync) { //如果是sync就是同步IO
return AIO_mode::SYNC;
} else if (req_type.is_log()) {
return AIO_mode::LOG;
} else {
return AIO_mode::NORMAL;
}
->並且按照讀寫分別放入
srv_stats.data_read.add
srv_stats.data_written.add
中
->bpage = static_cast<buf_page_t *>(message)
->slot = mutex_acquire_and_get_space(page_id.space(), space)
為開啟的檔案分配slot,注意這裡還包含了關閉開啟的檔案,如果
超過了innodb_open_file_limits 引數
->fil_system->m_max_n_open <= s_n_open
->fil_system->close_file_in_all_LRU
->opened = prepare_file_for_io(file, false)
Fil_shard::prepare_file_for_io
->if (!open_file(file, extend))
Fil_shard::open_file
開啟檔案
->os_aio(os_aio_func)
這個函式是同步IO和非同步IO都是它調入主要看AIO_mode
-> if (aio_mode == AIO_mode::SYNC && !srv_use_native_aio)
-> if (type.is_read())
呼叫pread,直接讀取
{ return (os_file_read_func(type, name, file.m_file, buf, offset, n)) }
->os_file_read_page
->os_file_pread
++os_n_file_reads
os_n_pending_reads.fetch_add(1)
->os_file_io
os_n_pending_reads.fetch_sub(1)
-> 呼叫pwrite,直接寫入
return (os_file_write_func(type, name, file.m_file, buf, offset, n))
和上面差不多的路徑
如果不是同步IO就需要進行非同步IO,將請分配給非同步IO執行緒
-> array = AIO::select_slot_array(type, read_only, aio_mode)
-> slot = array->reserve_slot(type, m1, m2, file, name, buf, offset, n, e_block)
-> if (type.is_read())
-> if (srv_use_native_aio){
++os_n_file_reads
-> array->linux_dispatch(slot)
-> if (type.is_write())
-> if (srv_use_native_aio){
++os_n_file_writes
-> array->linux_dispatch(slot)
-> 這裡呼叫io_submit進行IO 提交,隨後由非同步IO執行緒進行收集
-> return (DB_SUCCESS)
/* AIO request was queued successfully! */
->if (sync) {
如果是同步IO ,注意這裡是同步IO,不是fsync
->complete_io(file, req_type)
Fil_shard::complete_io
->--file->n_pending;
pending IO 減1
->if (type.is_write())
3.2 非同步IO執行緒處理
fil_aio_wait
->os_aio_handler
->os_aio_linux_handler
->handler.poll
主要看做IO的收集的部分,因為IO正常是使用者執行緒自己提交的
->slot = find_completed_slot(&n_pending); //這裡看應該是IO比較慢的情況
找到可能的partial IO slot,透過迴圈整個segment中的solt完成
->LinuxAIOHandler::find_completed_slot
->for (ulint i = 0; i < m_n_slots; ++i, ++slot)
-> if(slot->is_reserved)
迴圈整個segment中的slot情況,如果存在,++*n_pending
增加pending IO
->if (slot->io_already_done)
如果IO已經完成
->return (slot)
返回這個slot
->if (slot != nullptr)
如果slot不為null,這檢查是否是否為partial IO slot
如果是需要重新提交
->err = check_state(slot);
->如果err為DB_FAIL,這呼叫resubmit來提交IO
->else srv_set_io_thread_op_info(m_global_segment,
"waiting for completed aio requests");
這裡可以看出正在等待完成的aio 請求,如果有則負責收割IO
->collect()
LinuxAIOHandler::collect
->io_getevents()
非同步IO堵塞收集,這裡收割IO
->srv_set_io_thread_op_info(segment, "complete io for file"); ---
設定IO已經完成
->Fil_shard::complete_io
這個函式不管同步IO還是非同步IO都要呼叫,這個函式主要是對檔案資訊的維護
是否
->--file->n_pending;
pending IO 減少
->if (type.is_write())
如果是寫操作,則呼叫
->write_completed
->add_to_unflushed_list(file->space)
->UT_LIST_ADD_FIRST(m_unflushed_spaces, space);
這個list主要用於儲存需要flush的資料檔案,以便後面使用
->UT_LIST_ADD_FIRST(m_LRU, file)
如果是讀操作直接加入 file 的 LRU連結串列的頭部,如果淘汰file,這按照這個淘汰。
->switch (file->space->purpose)
如果是普通的page則呼叫
->srv_set_io_thread_op_info(segment, "complete io for buf page"); ---
->buf_page_io_complete(static_cast<buf_page_t *>(m2), false);
這個函式不管同步IO還是非同步IO都要呼叫
->switch (io_type)
->case BUF_IO_READ
->buf_page_set_io_fix(bpage, BUF_IO_NONE);
->buf_pool->n_pend_reads.fetch_sub(1);
->buf_pool->stat.n_pages_read.fetch_add(1);
->case BUF_IO_WRITE
->buf_flush_write_complete(bpage)
這個過程加buffer pool instance flush_state_mutex 鎖
還完成dblwr的解鎖
->buf_pool = buf_pool_from_bpage(bpage)
->buf_flush_remove(bpage);
從flush中刪除,同上
->buf_page_set_io_fix(bpage, BUF_IO_NONE);
清理IO狀態
->flush_state_mutex 解鎖
->dblwr::write_complete(bpage, flush_type)
->Double_write::write_complete
釋放batch segment等資源
->fil_flush_file_spaces(FIL_TYPE_TABLESPACE)
負責對unflush的檔案進行刷盤
->fil_system->flush_file_spaces(purpose) Fil_system::flush_file_spaces
->for (auto shard : m_shards)
迴圈每個file shard
->shard->flush_file_spaces(purpose)
->for (auto space = UT_LIST_GET_FIRST(m_unflushed_spaces); space != nullptr;space = UT_LIST_GET_NEXT(unflushed_spaces, space))
迴圈每個unflushed datafile的檔案和purpose進行比對,如果目標相同則
space_ids.push_back(space->id)
space id放入到space_ids vector中
->迴圈space_ids 這裡已經是收集的做了刷盤的檔案
->做 space_flush(space_id)
非同步IO合併了刷盤的IO 請求,進行fsync的壓力也會變小
-> ++file.n_pending_flushes
增加pending IO,這個IO來自資料檔案的刷盤
-> os_file_flush(file.handle)
-> os_file_fsync_posix(file)
->--file.n_pending_flushes
減少pending IO
->if (flush_type == BUF_FLUSH_LRU)
->evict = true
需要從LRU中剔除page
->if (evict && buf_LRU_free_page(bpage, true))
同上,從hash結構和LRU中去掉這個page,並且反還給free list
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027826/viewspace-2995374/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- MySQL刷髒MySql
- MySQL學習之flush(刷髒頁)MySql
- mysql 相關MySql
- PG檢查點刷寫髒頁
- MySQL鎖相關MySql
- mysql 索引相關MySql索引
- Linux的磁碟快取和刷髒頁Linux快取
- MySQL InnoDB髒頁管理MySql
- PostgreSQL的"double buffers"刷髒機制和引數SQL
- 說說MySQL索引相關MySql索引
- MySQL效能相關引數MySql
- mysql效能監控相關MySql
- MySQL 相關子查詢MySql
- mysql load 相關實驗MySql
- mysql相關問題總結MySql
- MySQL 連線相關引數MySql
- mysql 官方架構相關圖MySql架構
- MySQL 之慢查詢相關操作MySql
- JDBC mysql 相關內容筆記JDBCMySql筆記
- mysql load 相關實驗記錄MySql
- 總結 MySQL 相關知識點MySql
- MySQL全面瓦解19:遊標相關MySql
- 推薦幾款MySQL相關工具MySql
- Mysql的優化的相關知識MySql優化
- mysql relay log相關引數說明MySql
- 面試小知識:MySQL索引相關面試MySql索引
- MySQL中鎖的相關問題DTQUMySql
- MySQL索引分類及相關概念辨析MySql索引
- MySQL 中的約束及相關操作MySql
- MySQL單詞搜尋相關度排名MySql
- MySQL全面瓦解17:觸發器相關MySql觸發器
- MySQL 字串擷取相關函式總結MySql字串函式
- Mysql 顯示錶的相關資訊 --命令MySql
- MySQL 資料庫相關流程圖 / 原理圖MySql資料庫流程圖
- 倒排索引及ES相關概念對比MySQL索引MySql
- MySQL慢查詢日誌相關設定MySql
- MySQL 5.7 學習心得之安全相關特性MySql
- MySQL 8.0 18個管理相關的新特性MySql