真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

MySQL中InnoDB內部機制的示例分析

這篇文章主要介紹了MySQL中InnoDB內部機制的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

成都創(chuàng)新互聯(lián)公司專注于企業(yè)成都營銷網(wǎng)站建設、網(wǎng)站重做改版、徐聞網(wǎng)站定制設計、自適應品牌網(wǎng)站建設、HTML5建站商城網(wǎng)站制作、集團公司官網(wǎng)建設、外貿網(wǎng)站建設、高端網(wǎng)站制作、響應式網(wǎng)頁設計等建站業(yè)務,價格優(yōu)惠性價比高,為徐聞等各大城市提供網(wǎng)站開發(fā)制作服務。

Read view

InnoDB支持MVCC多版本,其中RC(Read Committed)和RR(Repeatable Read)隔離級別是利用consistent read view(一致讀視圖)方式支持的。 所謂consistent read view就是在某一時刻給事務系統(tǒng)trx_sys打snapshot(快照),把當時trx_sys狀態(tài)(包括活躍讀寫事務數(shù)組)記下來,之后的所有讀操作根據(jù)其事務ID(即trx_id)與snapshot中的trx_sys的狀態(tài)作比較,以此判斷read view對于事務的可見性。

Read view中保存的trx_sys狀態(tài)主要包括

  • low_limit_id:high water mark,大于等于view->low_limit_id的事務對于view都是不可見的

  • up_limit_id:low water mark,小于view->up_limit_id的事務對于view一定是可見的

  • low_limit_no:trx_no小于view->low_limit_no的undo log對于view是可以purge的

  • rw_trx_ids:讀寫事務數(shù)組

RR隔離級別(除了Gap鎖之外)和RC隔離級別的差別是創(chuàng)建snapshot時機不同。 RR隔離級別是在事務開始時刻,確切地說是第一個讀操作創(chuàng)建read view的;RC隔離級別是在語句開始時刻創(chuàng)建read view的。

創(chuàng)建/關閉read view需要持有trx_sys->mutex,會降低系統(tǒng)性能,5.7版本對此進行優(yōu)化,在事務提交時session會cache只讀事務的read view。

下次創(chuàng)建read view,判斷如果是只讀事務并且系統(tǒng)的讀寫事務狀態(tài)沒有發(fā)生變化,即trx_sys的max_trx_id沒有向前推進,而且沒有新的讀寫事務產(chǎn)生,就可以重用上次的read view。

Read view創(chuàng)建之后,讀數(shù)據(jù)時比較記錄最后更新的trx_id和view的high/low water mark和讀寫事務數(shù)組即可判斷可見性。

如前所述,如果記錄最新數(shù)據(jù)是當前事務trx的更新結果,對應當前read view一定是可見的。

除此之外可以通過high/low water mark快速判斷:

  • trx_id < view->up_limit_id的記錄對于當前read view是一定可見的;

  • trx_id >= view->low_limit_id的記錄對于當前read view是一定不可見的;

如果trx_id落在[up_limit_id, low_limit_id),需要在活躍讀寫事務數(shù)組查找trx_id是否存在,如果存在,記錄對于當前read view是不可見的。

由于InnoDB的二級索引只保存page最后更新的trx_id,當利用二級索引進行查詢的時候,如果page的trx_id小于view->up_limit_id,可以直接判斷page的所有記錄對于當前view是可見的,否則需要回clustered索引進行判斷。

如果記錄對于view不可見,需要通過記錄的DB_ROLL_PTR指針遍歷history list構造當前view可見版本數(shù)據(jù)。

回滾段

InnoDB也是采用回滾段的方式構建old version記錄,這跟Oracle方式類似。

記錄的DB_ROLL_PTR指向最近一次更新所創(chuàng)建的回滾段;每條undo log也會指向更早版本的undo log,從而形成一條更新鏈。通過這個更新鏈,不同事務可以找到其對應版本的undo log,組成old version記錄,這條鏈就是記錄的history list。

分配rollback segment

MySQL 5.6對于沒有顯示指定READ ONLY事務,默認為是讀寫事務。在事務開啟時刻分配trx_id和回滾段,并把當前事務加到trx_sys的讀寫事務數(shù)組中。

5.7版本對于所有事務默認為只讀事務,遇到第一個寫操作時,只讀事務切換成讀寫事務分配trx_id和回滾段,并把當前事務加到trx_sys的讀寫事務數(shù)組中。

分配回滾段的工作在函數(shù)trx_assign_rseg_low進行,分配策略是采用round-robin方式。

從5.6開始支持獨立的undo表空間,InnoDB支持128個undo回滾段,請參照第1篇文章。

  • rseg0:預留在系統(tǒng)表空間ibdata中

  • rseg1~rseg32:這32個回滾段存放于臨時表的系統(tǒng)表空間中

  • rseg33~rseg127:根據(jù)配置存放到獨立undo表空間中(如果沒有打開獨立Undo表空間,則存放于ibdata中)

trx_assign_rseg_low判斷,如果支持獨立的undo表空間,在undo表空間有可用回滾段的情況下避免使用系統(tǒng)表空間的回滾段。

rseg->skip_allocation為TRUE表示rseg所在的表空間要被truncate,應該避免使用此rseg分配回滾段。此種情況,必須保證有至少2個活躍的undo表空間,并且至少2個活躍的undo slot。

分配成功時,遞增rseg->trx_ref_count,保證rseg的表空間不會被truncate。

臨時表操作不記redo log,最終調用get_next_noredo_rseg函數(shù)進行分配;其他情況調用get_next_redo_rseg。

回滾段實際上是undo文件組織方式,每個回滾段維護了一個段頭頁(segment header),該page劃分了1024個slot(TRX_RSEG_N_SLOTS),每個slot對應到一個undo log對象。

理論上,InnoDB最多支持 96 (128 - 32 /* temp-tablespace */) * 1024個普通事務。

但如果是臨時表的事務,可能還需要多分配1個slot(臨時表的系統(tǒng)表空間)。

  • 只讀階段為臨時表分配的,在臨時表的系統(tǒng)表空間中分配

  • 讀寫階段在undo表空間分配

分配undo log

Insert數(shù)據(jù)只對當前事務或者提交之后可見,所以insert的undo log在事務commit后就可以釋放了。

Update/delete的undo記錄通常用來維護old version記錄,為查詢提供服務;只有當trx_sys中沒有任何view需要訪問那個old version的數(shù)據(jù)時才可以被釋放。

InnoDB對insert和update/delete分配不同的undo slot

  • insert的undo slot記在trx->rsegs.m_redo.insert_undo,調用trx_undo_assign_undo分配

  • update的undo slot記在trx->rsegs.m_redo.undate_undo,調用trx_undo_assign_undo分配

trx_undo_assign_undo

I. 檢查cached隊列是否有緩存的undo log(內存中數(shù)據(jù)結構是trx_undo_t)

  • 如果存在,把這個undo log從cached隊列移除

  • reuse的邏輯:

    a.insert undo:重新初始化undo page的header信息(trx_undo_insert_header_reuse),并在redo log記一條MLOG_UNDO_HDR_REUSE日志

    b.update undo:在undo page的header上分配新的undo header(trx_undo_header_create),并在redo log記一條MLOG_UNDO_HDR_CREATE日志

  • 預留xid空間

  • 重新初始化undo(trx_undo_mem_init_for_reuse)把undo->state設置為TRX_UNDO_ACTIVE,并把undo->state寫入到第一個undo page的TRX_UNDO_SEG_HDR+TRX_UNDO_STATE位置上

注1:TRX_UNDO_SEG_HDR表示segment header起始offset 注2:undo segment與事務trx是一一對應關系,undo segment header的狀態(tài)(TRX_UNDO_STATE)跟事務當前狀態(tài)也是一一對應的

如下圖(引自第1篇文章)

MySQL中InnoDB內部機制的示例分析

undo segment是個獨立的段,每個undo segment包含1個header page(第1個undo page)和若干個記錄undo日志的undo page。

第1個undo page中存儲的是元信息: 首先存儲的是undo page的元信息,位于TRX_UNDO_PAGE_HDR到TRX_UNDO_SEG_HDR之間。

TRX_UNDO_PAGE_START:指向page中第一個undo log TRX_UNDO_PAGE_FREE:指向page中下一個undo log要寫到的位置 TRX_UNDO_PAGE_NODE:undo segment所有page組成一個雙向鏈表,每個page的TRX_UNDO_PAGE_NODE字段作為連接件,第一個undo page中的TRX_UNDO_PAGE_LIST作為表頭

 /* undo page header */ #define TRX_UNDO_PAGE_HDR FSEG_PAGE_DATA #define TRX_UNDO_PAGE_TYPE 0 /*!< TRX_UNDO_INSERT or
                    TRX_UNDO_UPDATE */ #define TRX_UNDO_PAGE_START 2 /*!< Byte offset where the undo log
                    records for the LATEST transaction
                    start on this page (remember that
                    in an update undo log, the first page
                    can contain several undo logs) */ #define TRX_UNDO_PAGE_FREE 4 /*!< On each page of the undo log this
                    field contains the byte offset of the
                    first free byte on the page */ #define TRX_UNDO_PAGE_NODE 6 /*!< The file list node in the chain
                    of undo log pages */ /*-------------------------------------------------------------*/ #define TRX_UNDO_PAGE_HDR_SIZE (6 + FLST_NODE_SIZE) /*!< Size of the transaction undo
                    log page header, in bytes */

之后是undo segment的元信息,位于TRX_UNDO_SEG_HDR到TRX_UNDO_SEG_HDR+TRX_UNDO_SEG_HDR_SIZE

TRX_UNDO_STATE:表示undo segment的狀態(tài),一個undo segment可以包含多個undo log,但至多只有1個active undo log,也就是最近的undo log TRX_UNDO_LAST_LOG:指向最近的undo log的header信息 TRX_UNDO_FSEG_HEADER:存儲的是undo segment對應的file segment信息,在fseg_create_general中設置(4字節(jié)space id,4字節(jié)的page no,2字節(jié)的page offset)

undo segment從buffer pool移除被persist到磁盤時,就寫到file segment指定的位置上

 #define TRX_UNDO_SEG_HDR    (TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE) #define TRX_UNDO_STATE      0   /*!< TRX_UNDO_ACTIVE, ... */ #define TRX_UNDO_LAST_LOG   2   /*!< Offset of the last undo log header on the segment header page, 0 if none */ #define TRX_UNDO_FSEG_HEADER    4   /*!< Header for the file segment which the undo log segment occupies */ #define TRX_UNDO_PAGE_LIST  (4 + FSEG_HEADER_SIZE) /*!< Base node for the list of pages in
                    the undo log segment; defined only on
                    the undo log segment's first page */ /*-------------------------------------------------------------*/ /** Size of the undo log segment header */ #define TRX_UNDO_SEG_HDR_SIZE   (4 + FSEG_HEADER_SIZE + FLST_BASE_NODE_SIZE)

再之后是undo log header信息,所有的undo log header都存儲在第一個undo page上。

II. 從cached隊列分配undo失敗時,需要真正分配一個undo segment(trx_undo_seg_create)

首先要從rseg分配一個slot(trx_rsegf_undo_find_free),每個rseg至多支持1024個slot。找到空slot返回index。

如果當前rseg已滿,trx_undo_seg_create返回DB_TOO_MANY_CONCURRENT_TRXS向上層報錯,表示并發(fā)事務太多無法創(chuàng)建undo segment。

然后在rseg對應的table space創(chuàng)建一個新的file segment,file segment信息記在segment header的TRX_UNDO_FSEG_HEADER(fseg_create_general)。

trx_undo_seg_create在創(chuàng)建file segment之后,把新創(chuàng)建segment的page no寫到rseg對應slot上建立映射關系,并返回新創(chuàng)建segment的page。

file segment與undo segment的映射關系,還有rseg[slot]與file segment對應page的映射關系都是在trx_undo_seg_create綁定的。cached undo不會更新這兩個映射關系。

III. trx_undo_seg_create返回的page上創(chuàng)建新的undo header;上層負責初始化trx_undo_t數(shù)據(jù)結構

trx_undo_create為新創(chuàng)建的undo header創(chuàng)建內存數(shù)據(jù)結構trx_undo_t(trx_undo_mem_create),把undo->state設置為TRX_UNDO_ACTIVE。

IV. 分配好的trx_undo_t會加入到事務的insert_undo_list或者update_undo_list隊列上

寫入undo log

trx_undo_assign_undo分配undo之后,就可往其中寫入undo記錄。寫入的page來自undo->last_page_no,初始情況下等于hdr_page_no。

update undo包含一個重要的部分:記錄的當前回滾段指針要寫到undo log里面,以便維護記錄的歷史數(shù)據(jù)鏈。

read view需要讀老版本數(shù)據(jù)時,會通過記錄中當前的回滾段指針開始向前找到可見版本的數(shù)據(jù)。

完成Undo log寫入后,構建新的回滾段指針并返回(trx_undo_build_roll_ptr),這個指針也就是clustered索引記錄的DB_ROLL_PTR。

回滾段指針包括rseg->id、日志所在的page no、以及page內偏移量,需要記錄到clustered索引記錄中。這里rseg->id用來確定rseg->space,真正用于定位undo log位置的其實是space, undo->page,undo->page_offset>三元組。

事務prepare

設置undo->state為TRX_UNDO_PREPARED,并把這個狀態(tài)寫到第一個undo page的(TRX_UNDO_SEG_HDR+TRX_UNDO_STATE)位置上。

除此之外,prepare階段還要更新xid信息。

事務commit

在事務commit階段,需要把undo->state設置為完成狀態(tài),并把undo加到undo segment的history list。正在提交的undo header被指向history list的第一項,表示當前事務history list最近的undo。

undo->state完成狀態(tài)包括3種,在trx_undo_set_state_at_finish設置

  • undo只占一個page,而且第一個undo page已使用的空間小于3/4 (TRX_UNDO_PAGE_REUSE_LIMIT):狀態(tài)設置為TRX_UNDO_CACHED

  • 不滿足1的情況下,如果是insert_undo(TRX_UNDO_INSERT):狀態(tài)設置為TRX_UNDO_TO_FREE

  • 不滿足1和2的情況下,狀態(tài)設置為TRX_UNDO_TO_PURGE,表示undo可能需要purge線程清理

cached undo會被到cached隊列上,這個隊列就是trx_undo_assign_undo提到的cached隊列

設置完undo->state之后,需要把這個狀態(tài)寫入到第一個undo page的(TRX_UNDO_SEG_HDR+TRX_UNDO_STATE)位置上

把undo加到undo segment header的history list

Insert的old version沒有實際意義,所以insert undo在事務commit時就可以釋放了。

trx_undo_set_state_at_finish里面有cached策略,如果只占1個undo page,并且undo page已使用的空間不足pagesize的3/4可以被reuse,其實大部分insert undo都屬于這種情況。

Update undo需要維護history list。這里先提一下trx->no,它維護了事務trx commit順序,跟事務的trx_id一樣,也是使用max_trx_id遞增產(chǎn)生。

另外,purge_sys(purge的全局數(shù)據(jù)結構)維護個最小堆,每個rollback segment第1次事務提交時向最小堆插入數(shù)據(jù),旨在找到trx_no最小的rollback segment進行purge。后面每次處理完1個rseg后,會把下一個undo記錄的trx_no壓入到這個最小堆,作為rseg的cursor。

事務commit時按照trx->no順序,把事務當前的undo log掛到undo segment history list的表頭,指向事務最近的undo log。

History list里的undo都是已提交事務的,當前事務所修改的undo log都記錄在這里,按照從新->老方式排列,最老的undo log在尾部。

undo加入到history list的方式是:以undo log的TRX_UNDO_HISTORY_NODE作為連接件,加入到第一個undo page的TRX_RSEG_HISTORY。

一般來說,每次調用trx_purge_add_update_undo_to_history都會把undo加入到history list,只有在undo page無法被reuse時才更新history list大?。梢哉J為是個優(yōu)化,最后一次更新history length)。

在此之后,trx_purge_add_update_undo_to_history會把undo log header的TRX_UNDO_TRX_NO更新為trx_no。

如果undo->del_marks是FALSE,這個函數(shù)也會更新TRX_UNDO_DEL_MARKS(undo segment創(chuàng)建或者reuse被初始化為TRUE),澄清這不是delete marker。

如果undo segment自創(chuàng)建以來(也可能是上次purge完成之后)中第1個事務commit,還需要更新purge有關的一些參數(shù),指向下次purge從哪里開始執(zhí)行。

老版本數(shù)據(jù)purge

舊版本數(shù)據(jù)不再被任何view訪問就可以被刪除了。5.6以上版本支持獨立purge線程,用戶可以通過參數(shù)Innodb_purge_threads設置purge線程個數(shù)。

有兩類purge線程:

  • coordinator thread:srv_purge_coordinator_thread,全局只有1個

  • worker thread:srv_worker_thread,系統(tǒng)有innodb_purge_threads - 1個

coordinator thread負責啟動worker thread參與到purge工作中。

增加purge線程的策略是:trx_sys->rseg_history_len比上次循環(huán)變大了或者rseg_history_len超過某一閾值,需要引進更多的worker thread。

減少purge線程的策略是:如果之前使用多個purge 線程,trx_sys->rseg_history_len并沒有變大,可能需要減少worker thread。

在進行purge之前,首先要確定purge線程要做哪些工作,也就是說哪些undo log可以被purged。

purge也是通過read view來確定工作范圍,被稱為purge view。如果系統(tǒng)有活躍read view,就選取最老的read view作為purge view。

如果不存在就給trx_sys的狀態(tài)打個snapshot,作為purge view,可以被purge的undo log其trx_no一定是小于系統(tǒng)中所有已提交事務的trx->no。

這里插一句,在事務commit時,會把產(chǎn)生的trx->no加入到trx_sys->serialisation_list鏈表,這個鏈表是按照trx->no升序次序排列,也就是維護了trx commit順序。

InnoDB初始化的時候會初始化purge_sys數(shù)據(jù)結構,其中一個工作就是創(chuàng)建purge graph。

這是總共3層結構的圖:

  • 第1層是fork節(jié)點

  • 第2次是thrd節(jié)點(表示purge thread)

  • 第3層是node節(jié)點(表示purge task)

所有的thrd節(jié)點被鏈入到fork->thrs鏈表中;fork地址存儲在purge_sys->query,可以通過purge_sys直接訪問。

執(zhí)行purge的時候總是遍歷purge_sys->query->thrs鏈表,給每個purge線程分配purge任務(trx_purge_attach_undo_recs)。

解析undo log的調用路徑如下:

 srv_purge_coordinator_thread -> srv_do_purge -> trx_purge ->
        trx_purge_attach_undo_recs -> trx_purge_fetch_next_rec -> 
               trx_purge_get_next_rec

purge_sys->next_stored為FALSE時,表示rseg_iter當前指向的rseg無效,需要把rseg_iter移到下一個有效的rseg(TrxUndoRsegsIterator::set_next)。

purge_sys->purge_queue維護了一個最小堆,每次pop最頂元素,可以得到trx_no最小的rollback segment(TrxUndoRsegsIterator::set_next)。

5.7支持臨時表的noredo的rollback segment,set_next遇到redo rollback segment和noredo rollback segment同時存在的情況會一股腦把這兩個rollback segment都pop出來加入到 purge_sys->rseg_iter->m_trx_undo_rsegs數(shù)組中,也在TrxUndoRsegsIterator::set_next實現(xiàn)。

如果沒有rollback segment需要purge話,purge_sys->rseg設置為NULL,purge線程會去睡眠(trx_purge_choose_next_log)。

一般情況下都是有rollback segment需要處理的,purge_sys->rseg更新成purge_sys->rseg_iter->m_trx_undo_rsegs的第1項(至多2項)。

purge_sys中的相應成員也要更新,指向當前rseg上次purge到的位置(TrxUndoRsegsIterator::set_next)。

update undo的del_marks域正常情況下都是TRUE,因為update/delete操作都需要對old value進行標記刪除。

如果purge_sys->rseg->last_del_marks是FALSE的話,表示這是一個dummy的undo log,不需要做物理刪除。這種情況下,把purge_sys->offset設置成0,做個標記表示這個undo log不需要被purged(trx_purge_read_undo_rec)。

正常情況下purge_sys->rseg->last_del_marks是TRUE,可以通過rseg->space, purge_sys->hdr_page_no, purge_sys->hdr_offset>讀取undo log記錄(trx_purge_read_undo_rec)。

并把purge_sys以下四個域設置成undo log記錄相應的信息(trx_purge_read_undo_rec)。

 purge_sys->offset = offset; /* undo log記錄的offset */ purge_sys->page_no = page_no; /* undo log記錄的pageno */ purge_sys->iter.undo_no = undo_no; /* undo log記錄的undo_no,trx內部undo的序列號 */ purge_sys->iter.undo_rseg_space = undo_rseg_space; /* undo log的tablespace */

為了保證purge_sys以上4個域一定是指向下一個有效undo log,每次讀取undo log時都會捎帶著讀取下一個undo log,并把上面這四個域更新為下一個undo log的信息,方面后續(xù)訪問(trx_purge_get_next_rec)。

如果是dummy undo,trx_purge_get_next_rec會去讀prev_undo(trx_purge_rseg_get_next_history_log),用prev_log信息更新rseg中下一個purge信息。

在此之后,還會把rseg->last_trx_no壓入最小堆,待后面繼續(xù)處理這個rseg。 然后調用trx_purge_choose_next_log選擇下一個處理的rseg,并讀取第一個undo log(trx_purge_get_next_rec)。

就這樣挨個讀取undo log,trx_purge_attach_undo_recs中有一個大循環(huán),每次調用trx_purge_fetch_next_rec讀到一個undo log后,把它存放到purge節(jié)點(purge graph的第三級節(jié)點) node->undo_recs數(shù)組里面,循環(huán)下一次執(zhí)行切換到下一個thr(purge 線程)。

循環(huán)的結束條件是:

  • 沒有新的undo log

  • 處理過的undo log達到batch size(一般是300)

達到循環(huán)結束條件后,trx_purge_attach_undo_recs返回。如果n_purge_threads > 1 (需要worker線程參與purge),coordinator線程會以round-robin方式啟動n_purge_threads - 1個worker線程。

不管有沒有worker線程參與purge,coordinator線程都會調用que_run_threads(在trx_purge上下文)去處理purge任務。

purge任務如何處理呢?通俗的說purge就是刪除被標記delete marker的記錄項。

大致過程如下:

 srv_purge_coordinator_thread -> srv_do_purge -> trx_purge ->
        que_run_threads -> que_run_threads_low -> que_thr_step
               row_purge_step -> row_purge -> row_purge_record ->
                       row_purge_del_mark -> row_purge_remove_sec_if_poss

一般刪除的原則是先刪除二級索引再刪除clustered索引(row_purge_del_mark)。

另一種情況是聚集索引in-place更新了,但二級索引上的記錄順序可能發(fā)生變化,而二級索引的更新總是標記刪除 + 插入,因此需要根據(jù)回滾段記錄去檢查二級索引記錄序是否發(fā)生變化,并執(zhí)行清理操作(row_purge_upd_exist_or_extern)。

前面提到過在parse undo log時,可能遇到dummy undo log。返回到row_purge執(zhí)行時需要判讀是否是dummy undo,如果是就什么也不做。

truncate undo space

trx_purge在處理完一個batch(通常是300)之后,調用trx_purge_truncate_historypurge_sys對每一個rseg嘗試釋放undo log(trx_purge_truncate_rseg_history)。

大致過程是:把每個purge過的undo log從history list移除,如果undo segment中所有的undo log都被釋放,可以嘗試釋放undo segment,這里隱式釋放file segment到達釋放存儲空間的目的。

感謝你能夠認真閱讀完這篇文章,希望小編分享的“MySQL中InnoDB內部機制的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持創(chuàng)新互聯(lián),關注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關知識等著你來學習!


本文名稱:MySQL中InnoDB內部機制的示例分析
文章分享:http://weahome.cn/article/ghdpoe.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部