《《MySQL運維內(nèi)參》節(jié)選》要點:
本文介紹了《MySQL運維內(nèi)參》節(jié)選,希望對您有用。如果有疑問,可以聯(lián)系我們。
引子:
書接上文,在之前六篇講述了寫日志,其實正常情況下,這都是無用功,因為根本用不到.上一節(jié)講到了,在什么情況下會用到日志,以及在什么時候會用到,如何用到等等內(nèi)容,我們這一節(jié)繼續(xù)講述,在掃描完成日志之后,如何做數(shù)據(jù)庫恢復(fù)工作,里面有什么邏輯,有什么可以改進(jìn)的地方等等,這都是我們讀者要去深思的地方.
(本書作者在“白家大院”齊聚首)
從這些代碼段中可以看到,緩存到HASH表之后,應(yīng)該是可以找合適的時機(jī)去APPLY了,那什么時候呢?我們可以返回去看看函數(shù)recv_scan_log_recs的最后,調(diào)用了函數(shù)recv_apply_hashed_log_recs,那這個就是我們要找的真正做APPLY的函數(shù)了.我們詳細(xì)看一下它的實現(xiàn).
繼續(xù):
從這些代碼段中可以看到,緩存到HASH表之后,應(yīng)該是可以找合適的時機(jī)去APPLY了,那什么時候呢?我們可以返回去看看函數(shù)recv_scan_log_recs的最后,調(diào)用了函數(shù)recv_apply_hashed_log_recs,那這個就是我們要找的真正做APPLY的函數(shù)了.我們詳細(xì)看一下它的實現(xiàn).
UNIV_INTERN void recv_apply_hashed_log_recs(
? ? ibool ? allow_ibuf
)? ?
{
? ? /* local vaiables … */
loop:
? ? recv_sys->apply_log_recs = TRUE;
? ? recv_sys->apply_batch_on = TRUE;
? ? /* 遍歷HASH表?是的,把HASH表中的每一個桶中的每一個頁面,連續(xù)處理 */
? ? for (i = 0; i < hash_get_n_cells(recv_sys->addr_hash); i++) {
? ? ? ? /* 遍歷HASH表一個桶中的多個地址 */
? ? ? ? for (recv_addr = static_cast<recv_addr_t*>(
? ? ? ? ? ? ? ? HASH_GET_FIRST(recv_sys->addr_hash, i));
?? ? ? ? ? ? recv_addr != 0;
?? ? ? ? ? ? recv_addr = static_cast<recv_addr_t*>(
? ? ? ? ? ? ? ? HASH_GET_NEXT(addr_hash, recv_addr))) {
? ? ? ? ? ? /* 針對每一個頁面,做這個頁面上所有的REDO操作 */
? ? ? ? ? ? ulint ? space = recv_addr->space;
? ? ? ? ? ? ulint ? zip_size = fil_space_get_zip_size(space);
? ? ? ? ? ? ulint ? page_no = recv_addr->page_no;
? ? ? ? ? ? if (recv_addr->state == RECV_NOT_PROCESSED) {
? ? ? ? ? ? ? ? mutex_exit(&(recv_sys->mutex));
? ? ? ? ? ? ? ? if (buf_page_peek(space, page_no)) {
? ? ? ? ? ? ? ? ? ? buf_block_t*? ? block;
? ? ? ? ? ? ? ? ? ? mtr_start(&mtr);
? ? ? ? ? ? ? ? ? ? block = buf_page_get(
? ? ? ? ? ? ? ? ? ? ? ? space, zip_size, page_no,
? ? ? ? ? ? ? ? ? ? ? ? RW_X_LATCH, &mtr);
? ? ? ? ? ? ? ? ? ? buf_block_dbg_add_level(
? ? ? ? ? ? ? ? ? ? ? ? block, SYNC_NO_ORDER_CHECK);
? ? ? ? ? ? ? ? ? ? /* 恢復(fù)一個頁面的數(shù)據(jù),APPLY recv_addr中存儲的所有REDO記錄,
? ? ? ? ? ? ? ? ? ? 這里使用了一個MTR來恢復(fù).需要注意的是,這個MTR只是用來
? ? ? ? ? ? ? ? ? ? 獲取頁面時,給這個頁面加鎖使用的,而不會涉及REDO操作,因為
? ? ? ? ? ? ? ? ? ? REDO是不需要再寫日志的,所以不用擔(dān)心這個MTR涉及到的日志量
? ? ? ? ? ? ? ? ? ? 太大的問題 */
? ? ? ? ? ? ? ? ? ? recv_recover_page(FALSE, block);
? ? ? ? ? ? ? ? ? ? mtr_commit(&mtr);
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? /* 這里的操作是,如果上面的buf_page_peek沒有在Buffer Pool中
? ? ? ? ? ? ? ? ? ? 找到這個頁面,那這里就從文件中將這個頁面載入到Buffer Pool,
? ? ? ? ? ? ? ? ? ? 并且預(yù)讀32個頁面以提高性能.恢復(fù)方法與是一樣的.*/
? ? ? ? ? ? ? ? ? ? recv_read_in_area(space, zip_size, page_no);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? mutex_enter(&(recv_sys->mutex));
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? /* Wait until all the pages have been processed */
? ? while (recv_sys->n_addrs != 0) {
? ? ? ? mutex_exit(&(recv_sys->mutex));
? ? ? ? os_thread_sleep(500000);
? ? ? ? mutex_enter(&(recv_sys->mutex));
? ? }
? ? /* Wait for any currently run batch to end.?
? ? 如注釋所述,如果上面的操作做完了,則需要保證這些日志APPLY之后
? ? 要在ibdata及ibd(s)中落地,此時就會將Buffer Pool中全部的臟頁刷一遍
? ? 以保證已經(jīng)處理的這些日志失效.可能有人會問,如果在恢復(fù)的過程中,假設(shè)
? ? 就是這里吧,還沒有做刷盤操作,數(shù)據(jù)庫又掛了,那怎么辦?
? ? 其實沒事兒,整個恢復(fù)過程,日志也沒有寫,只是掃描了一遍,并且有可能在
? ? Buffer Pool中已經(jīng)寫了很多頁面,有可能這些頁面已經(jīng)因為LRU已經(jīng)刷過
? ? 了,但這些操作是可重入的,也就是說,數(shù)據(jù)庫再起來,可以重新做一次REDO
? ? 操作,直到做成功為止.*/
? ? success = buf_flush_list(ULINT_MAX, LSN_MAX, NULL);
? ? recv_sys->apply_log_recs = FALSE;
? ? recv_sys->apply_batch_on = FALSE;
? ? /* 將HASH表中緩存的所有內(nèi)容清空 */
? ? recv_sys_empty_hash();
? ? mutex_exit(&(recv_sys->mutex));
}
到這里,我們應(yīng)該已經(jīng)清楚了REDO數(shù)據(jù)庫恢復(fù)的整個過程,并且可以返回到函數(shù)recv_recovery_from_checkpoint_start_func中,看一下最后的說明,做完REOD之后,做一次檢查點以說明這次數(shù)據(jù)庫恢復(fù)已經(jīng)完成.
但這里我又有話說了,各位同學(xué)有沒有發(fā)現(xiàn)一個細(xì)節(jié),那就是InnoDB在辛辛苦苦的將所有日志分析并且根據(jù)不同頁面通過HASH表存儲之后,我們特別要注意下面兩點特征:
這兩個特征非常重要,因為我們知道,REDO日志的APPLY,與順序有關(guān)系,LSN小的,必定要比LSN大的先做APPLY,不然有可能造成數(shù)據(jù)的覆蓋.但這有一個前提就是同一個頁面,不同頁面之間是不存在這樣的問題的.
那我們想想,是不是只需要保證,同一個頁面的日志順序執(zhí)行其所有的日志記錄即可,而不同頁面就沒必要守這個規(guī)則了,答案是肯定的.
那目前InnoDB難道不是這樣做的么?上面代碼中我們已經(jīng)看到了,他是用了一個兩層循環(huán),掃描了整個HASH表,慢慢的一條條的做REDO恢復(fù).基于上面的分析,其實可以大膽的想象的一下,REDO恢復(fù)可以實現(xiàn)并行恢復(fù).按照桶的下標(biāo)為鍵值分配線程,那這樣同一個桶必然會分到同一個線程中去做,這樣自然保證了同一個頁面的執(zhí)行順序,而不同的桶之間的頁面是沒有關(guān)系的,自然就可以并行恢復(fù)了.
啊?可以這樣?這個想法,可能會讓那些把日志文件設(shè)置的很大,又經(jīng)常出現(xiàn)機(jī)器宕機(jī)問題的同學(xué)(上面已經(jīng)提到了他們)心潮澎湃,這樣性能提升的不只一點點了.
還是那句話,這個是需要把日志文件設(shè)置很大,并且經(jīng)常出現(xiàn)宕機(jī)時,優(yōu)化效果才明顯.有需求,就能解決,我們希望這個優(yōu)化會出現(xiàn)在某個版本中,少一些浪費的時間.
到現(xiàn)在為止,REDO日志的恢復(fù)就做完了,到這個時候,才真正體現(xiàn)了這個“累贅”的價值,感謝有你!
上面所講的,是使用REDO日志來恢復(fù)數(shù)據(jù)庫的過程,在它做完之后,整個數(shù)據(jù)庫就是完整的了,已經(jīng)保證了所有的數(shù)據(jù)庫表都沒有丟數(shù)據(jù)的情況,所有的數(shù)據(jù)庫頁面也已經(jīng)是完整的了.假設(shè)此時對數(shù)據(jù)庫做DML操作,也已經(jīng)是可以的了,但還有一個問題沒有處理,那就是此時的數(shù)據(jù)庫,存在臟數(shù)據(jù).因為有些事務(wù)沒有提交,但數(shù)據(jù)已經(jīng)存在了(舉一個例子,事務(wù)在做的過程中,日志已經(jīng)寫完并刷盤,就是沒有提交,此時數(shù)據(jù)庫掛了),那根據(jù)事務(wù)的ACID特性,這樣的數(shù)據(jù)就不應(yīng)該存在,此時InnoDB需要做的就是把這些事務(wù)回滾掉,這就用到了我們下面將要講的“數(shù)據(jù)庫回滾”.
(神形兼?zhèn)浒?另外那種霸氣也流露出來了)
回滾段的管理,也是有一個入口位置用來存儲回滾段的管理信息的,在InnoDB中,是用第6個頁面(5號)來管理的,這個頁面是專門用來存儲事務(wù)相關(guān)的信息的,我們先看看其頁面格式:
/** Transaction system header */
/*————————————————————- @{ */
#define TRX_SYS_TRX_ID_STORE? ? 0 ? /*!< the maximum trx id or trx
? ? ? ? ? ? ? ? ? ? number modulo
? ? ? ? ? ? ? ? ? ? TRX_SYS_TRX_ID_UPDATE_MARGIN
? ? ? ? ? ? ? ? ? ? written to a file page by any
? ? ? ? ? ? ? ? ? ? transaction; the assignment of
? ? ? ? ? ? ? ? ? ? transaction ids continues from
? ? ? ? ? ? ? ? ? ? this number rounded up by
? ? ? ? ? ? ? ? ? ? TRX_SYS_TRX_ID_UPDATE_MARGIN
? ? ? ? ? ? ? ? ? ? plus
? ? ? ? ? ? ? ? ? ? TRX_SYS_TRX_ID_UPDATE_MARGIN
? ? ? ? ? ? ? ? ? ? when the database is
? ? ? ? ? ? ? ? ? ? started */
#define TRX_SYS_FSEG_HEADER 8 ? /*!< segment header for the
? ? ? ? ? ? ? ? ? ? tablespace segment the trx
? ? ? ? ? ? ? ? ? ? system is created into */
#define TRX_SYS_RSEGS ? ? ? (8 + FSEG_HEADER_SIZE)
? ? ? ? ? ? ? ? ? ? /*!< the start of the array of
? ? ? ? ? ? ? ? ? ? rollback segment specification
? ? ? ? ? ? ? ? ? ? slots */
上面定義的是第6號頁面中存儲的信息及其對應(yīng)的位置,每一項的詳細(xì)意義如下:
而針對每一個回滾段,即上面數(shù)組中的一個元素,也有其自己的存儲格式,代碼中的宏定義如下:
#define TRX_RSEG_MAX_SIZE ? 0 ? /* Maximum allowed size for rollback
? ? ? ? ? ? ? ? ? ? segment in pages */
#define TRX_RSEG_HISTORY_SIZE ? 4 ? /* Number of file pages occupied
? ? ? ? ? ? ? ? ? ? by the logs in the history list */
#define TRX_RSEG_HISTORY? ? 8 ? /* The update undo logs for committed
? ? ? ? ? ? ? ? ? ? transactions */
#define TRX_RSEG_FSEG_HEADER? ? (8 + FLST_BASE_NODE_SIZE)
? ? ? ? ? ? ? ? ? ? /* Header for the file segment where
? ? ? ? ? ? ? ? ? ? this page is placed */
#define TRX_RSEG_UNDO_SLOTS (8 + FLST_BASE_NODE_SIZE + FSEG_HEADER_SIZE)
? ? ? ? ? ? ? ? ? ? /* Undo log segment slots */
上面這些信息的存儲,是從頁面偏移38的位置開始的,在這個位置之前,存儲的是文件管理的信息(講參考索引管理相關(guān)章節(jié)),從38開始,存儲了上面5個信息,它們的意義分別如下:
這5個信息,存儲了一個回滾段的信息,最后一個位置的數(shù)組,就是用來真正存儲回滾段的位置,我們后面會講到這128*1024個槽是如何使用的.
根據(jù)上面的講述,我們現(xiàn)在已經(jīng)知道所有回滾段的存儲架構(gòu)了,如下圖所示:
現(xiàn)在就可以知道,InnoDB中支持的回滾段總共有128*1024=131072個,TRX_RSEG_UNDO_SLOTS數(shù)組的每個元素指向一個頁面,這個頁面對應(yīng)一個段,頁面號就是段首頁的頁面號.
在每一個事務(wù)開始的時候,都會分配一個rseg,就是從長度為128的數(shù)組中,根據(jù)最近使用的情況,找到一個臨近位置的rseg,在這個事務(wù)的生命周期內(nèi),被分配的rseg就會被這個事務(wù)所使用.
在事務(wù)執(zhí)行過程中,會產(chǎn)生兩種回滾日志,一種是INSERT的UNDO記錄,一種是UPDATE的UNDO記錄,可能有人會問DELETE哪去了?其實是包含在UPDATE的回滾記錄中的,因為InnoDB把UNDO分為兩類,一類就是新增,也就是INSERT,一類就是修改,就是UPDATE,分類的依據(jù)就是事務(wù)提交后要不要做PURGE操作,因為INSERT是不需要PURGE的,只要事務(wù)提交了,那這個回滾記錄就可以丟掉了,而對于更新和刪除操作而言,如果事務(wù)提交了,還需要為MVCC服務(wù),那就需要將這些日志放到History List中去,等待去做PURGE,以及MVCC的多版本查詢等,所以分為兩類.
所以,一個事務(wù)被分配了一個rseg之后,通常情況下,如果一個事務(wù)中既有插入,又有更新(或刪除),那這個事務(wù)就會對應(yīng)兩個UNDO段,即在一個rseg的1024個槽中,要使用兩個槽來存儲這個事務(wù)的回滾段,一個是插入段,一個是更新段.
在事務(wù)要存儲回滾記錄的時候,事務(wù)就要從1024個槽中,根據(jù)相應(yīng)的更新類型(插入或者更新)找到空閑的槽來作為自己的UNDO段.如果已經(jīng)申請過相同類型的UNDO段,就直接使用,否則就需要新創(chuàng)建一個段,并將段首頁號寫入到這個rseg的長度為1024的數(shù)組的對應(yīng)位置(空閑位置)中去,這樣就將具體的回滾段與整個架構(gòu)聯(lián)系起來了.
如果在1024個槽中找不到空閑的位置,那這個事務(wù)就會被回滾掉,報出錯誤為:“Too many active concurrent transactions”,錯誤號為1637的異常.當(dāng)然這種情況一般不會見到,如果能把這個用完,估計數(shù)據(jù)庫已經(jīng)根本動不了了.
上面講述了整個回滾段存儲架構(gòu)及與事務(wù)的相關(guān)性,具體到一個事務(wù)所使用的某個回滾段的管理,就存儲在了回滾段首頁中,管理信息包括三部分,分別是Undo page header、Undo segment header及Undo log header.下面分別介紹:
Undo page header:
/** Transaction undo log page header offsets */
#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 */
Undo segment header:
/** Undo log segment header */
#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 */
Undo log header:
/** The undo log header. There can be several undo log headers on the first
page of an update undo log segment. */
#define TRX_UNDO_TRX_ID ? ? 0 ? /*!< Transaction id */
#define TRX_UNDO_TRX_NO ? ? 8 ? /*!< Transaction number of the
? ? ? ? ? ? ? ? ? ? transaction; defined only if the log
? ? ? ? ? ? ? ? ? ? is in a history list */
#define TRX_UNDO_DEL_MARKS? 16? /*!< Defined only in an update undo
? ? ? ? ? ? ? ? ? ? log: TRUE if the transaction may have
? ? ? ? ? ? ? ? ? ? done delete markings of records, and
? ? ? ? ? ? ? ? ? ? thus purge is necessary */
#define TRX_UNDO_LOG_START? 18? /*!< Offset of the first undo log record
? ? ? ? ? ? ? ? ? ? of this log on the header page; purge
? ? ? ? ? ? ? ? ? ? may remove undo log record from the
? ? ? ? ? ? ? ? ? ? log start, and therefore this is not
? ? ? ? ? ? ? ? ? ? necessarily the same as this log
? ? ? ? ? ? ? ? ? ? header end offset */
#define TRX_UNDO_XID_EXISTS 20? /*!< TRUE if undo log header includes
? ? ? ? ? ? ? ? ? ? X/Open XA transaction identification
? ? ? ? ? ? ? ? ? ? XID */
#define TRX_UNDO_DICT_TRANS 21? /*!< TRUE if the transaction is a table
? ? ? ? ? ? ? ? ? ? create, index create, or drop
? ? ? ? ? ? ? ? ? ? transaction: in recovery
? ? ? ? ? ? ? ? ? ? the transaction cannot be rolled back
? ? ? ? ? ? ? ? ? ? in the usual way: a ‘rollback’ rather
? ? ? ? ? ? ? ? ? ? means dropping the created or dropped
? ? ? ? ? ? ? ? ? ? table, if it still exists */
#define TRX_UNDO_TABLE_ID ? 22? /*!< Id of the table if the preceding
? ? ? ? ? ? ? ? ? ? field is TRUE */
#define TRX_UNDO_NEXT_LOG ? 30? /*!< Offset of the next undo log header
? ? ? ? ? ? ? ? ? ? on this page, 0 if none */
#define TRX_UNDO_PREV_LOG ? 32? /*!< Offset of the previous undo log
? ? ? ? ? ? ? ? ? ? header on this page, 0 if none */
#define TRX_UNDO_HISTORY_NODE ? 34? /*!< If the log is put to the history
? ? ? ? ? ? ? ? ? ? list, the file list node is here */
這是一個針對UNDO日志的頭信息,一個事務(wù)寫入一次UNDO日志就會創(chuàng)建一個UNDO日志單元,都會對應(yīng)一個這樣的UNDO日志頭信息,用來管理這個日志信息的狀態(tài),存儲一些相關(guān)的信息以備恢復(fù)時使用,多個UNDO日志之間,通過雙向鏈表連接起來(通過我們即將介紹的TRX_UNDO_NEXT_LOG及TRX_UNDO_PREV_LOG來管理).
到現(xiàn)在為止,關(guān)于具體一個UNDO段中每個頁面及頁面內(nèi)容是如何管理的已經(jīng)清楚了,當(dāng)一個事務(wù)需要寫入UNDO日志時,就可以直接從對應(yīng)的UNDO段中找到一個頁面及對應(yīng)的追加日志的偏移位置,然后將對應(yīng)的UNDO日志寫入即可.
(還是昨天那個球,線下討論了關(guān)于會源碼的作用,有很多人認(rèn)為,一提到某人會源碼,就覺得一般是用來裝一下的,并且實際上沒有幾家公司強(qiáng)大到放心地讓你寫源碼去,所以覺得這不是一個名副其實的技能.我認(rèn)為實則不然,會源碼,99%的機(jī)會不是用來寫源碼的,而是閱讀其實際方法及原理,盡可能的做到知MySQL,或者在出現(xiàn)問題之后,是一個很好的用來解決問題的方法,要知道,我不只一次碰到手冊上面所述內(nèi)容是錯誤的,并且有很多問題網(wǎng)上也是沒有的,那可想而知,如果你會閱讀源碼了,這樣的問題可以立刻迎刃而解)
日志這篇,以這樣的角度及思維模式來講述真的是沒有見過的,并且到今天已經(jīng)是連載第七篇,
可以很確定的告訴大家,還有幾篇明天繼續(xù)發(fā).
就在今天,關(guān)于這篇內(nèi)容,我們的韓朱忠老師也說話了:
他也是我們本書的推薦序之一.在這里謝謝他.
文章來自微信公眾號:DBAce
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.snjht.com/jiaocheng/4171.html