成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

MySQL 崩潰恢復過程分析

數據庫 MySQL
本文介紹的崩潰恢復過程,包含 Server 層和 InnoDB,不涉及其它存儲引擎,內容基于 MySQL 8.0.29 源碼。

天有不測風云,數據庫有旦夕禍福。

前面寫 Redo 日志的文章介紹過,數據庫正常運行時,Redo 日志就是個累贅。

現在,終于到了 Redo 日志揚眉吐氣,大顯身手的時候了。

本文我們一起來看看,MySQL 在崩潰恢復過程中都干了哪些事情,Redo 日志又是怎么大顯身手的。

本文介紹的崩潰恢復過程,包含 server 層和 InnoDB,不涉及其它存儲引擎,內容基于 MySQL 8.0.29 源碼。

正文

1、概述

MySQL 崩潰也是一次關閉過程,只是比正常關閉著急了一些。

正常關閉時,MySQL 會做一系列收尾工作,例如:清理 undo 日志、合并 change buffer 緩沖區等操作。

具體會進行哪些收尾工作,取決于系統變量 innodb_fast_shutdown 的配置。

崩潰直接就是戛然而止,撂挑子不干了,還沒來得及進行的那些收尾工作怎么辦?

那就只能等待下次啟動的時候再干了,這就是本文要介紹的崩潰恢復過程。

2、讀取兩次寫頁面

MySQL 一旦崩潰,Redo 日志就要去拯救世界了(MySQL 就是它的世界),Redo 日志拯救世界的方式就是把還沒來得及刷盤的臟頁恢復到崩潰之前那一刻的狀態。

雖然 Redo 日志能夠用來恢復數據頁,但這是有前提條件的:數據頁必須完好無損的狀態。

本文我們把系統表空間、獨立表空間、undo 表空間中的頁統稱為數據頁。

如果數據頁剛寫了一半,MySQL 就戛然而止,這個數據頁就損壞了,面對這種情況,Redo 日志也是巧婦難為無米之炊。

Redo 日志拯救世界之路就要因為這個問題停滯不前嗎?

那顯示是不能的,這就該輪到兩次寫上場了。

兩次寫?的官方名字是 double write?,它包含內存緩沖區?和 dblwr 文件兩個部分,InnoDB 臟頁刷盤前,都會先把臟頁寫入內存緩沖區,再寫入 dblwr 文件,成功之后才會把臟頁刷盤。

兩次寫通過系統變量 innodb_doublewrite? 控制開啟或關閉,本文內容基于該系統變量的默認值 ON,表示開啟兩次寫。

如果臟頁寫入內存緩沖區和 dblwr 文件的程中,MySQL 崩潰了,表空間中對應的數據頁還是完整的,下次啟動時,不需要用兩次寫頁面修復這個數據頁。

如果臟頁刷盤時,MySQL 崩潰了,表空間對應的數據頁損壞了,下次啟動時,應用 Redo 日志到數據頁之前?,需要用兩次寫頁面修復這個數據頁。

dblwr 文件 默認位于 MySQL 數據目錄下:

[csch@csch /usr/local/mysql_8_0_29/data] ls -l | grep dblwr
-rw-r----- 1 csch staff 192K 8 27 12:04 #ib_16384_0.dblwr
-rw-r----- 1 csch staff 8.2M 8 1 16:29 #ib_16384_1.dblwr

MySQL 啟動過程中,會把 *.dblwr 文件中的所有兩次寫頁面加載到兩次寫內存緩沖區,并用內存緩沖區中的兩次寫頁面修復損壞的數據頁,然后再應用 Redo 日志到數據頁。

3、恢復數據頁

應用 Redo 日志到數據頁(3.4 小節),需要先讀取 Redo 日志(3.3 小節)。

讀取日志 Redo 日志,需要有個起點,起點就是最后一次 checkpoint 的 lsn(3.1 小節)。

應用 Redo 日志有一個前提:數據頁必須是完好無損的。要保證數據頁的完整性,應用 Redo 日志之前需要修復損壞的數據頁(3.2 小節)。

修復損壞數據頁只需要保證在應用 Redo 日志之前就行了,之所以安排在 3.2 小節,是遵循了源碼中的順序。

了解本節安排內容順序的邏輯,有助于理解應用 Redo 日志恢復數據頁的過程,接下來我們正式進入下一個環節。

(1)找到 last_checkpoint_lsn

讀取 Redo 日志之前,必須先確定一個起點,這個起點就是 InnoDB 最后一次 checkpoint 操作的 lsn,也就是 last_checkpoint_lsn。

每個 Redo 日志文件的前 4 個 block 都是保留空間,不會用來寫 Redo 日志,last_checkpoint_lsn? 和其它 checkpoint 信息一起,位于第 1 個 Redo 日志文件的第 2、4 個 block 中。

Redo 日志文件中每個 block 的大小為 512 字節。

InnoDB 每次進行 checkpoint 操作時,都會把 checkpoint_no 加 1,用于標識一次 checkpoint 操作。

然后把本次 checkpoint 信息寫入 Redo 日志文件的第 2 或第 4 個 block 中。具體寫入哪個 block,取決于 checkpoint_no。

如果 checkpoint_no 是奇數,checkpoint 信息寫入第 4 個 block。

如果 checkpoint_no 是偶數,checkpoint 信息寫入第 2 個 block。

確定讀取 Redo 日志的起點時,從第 2、4 個 block 中讀取較大的那個 last_checkpoint_lsn 作為起點。

為什么 checkpoint 信息要存儲到 2 個 block 中?

這是一個用于保證 checkpoint 信息安全性的簡單好用的方法,因為每次 checkpoint 只會往其中一個 block 寫入信息。

萬一就在某次寫 checkpoint 信息的過程中 MySQL 崩潰了,有可能導致正在寫入的這個 block 中的 checkpoint 信息不正確。

這種情況下,另一個 block 中的 checkpoint 信息肯定是正確的了,因為它里面的信息是上一次正常寫入的。

能夠用這種冗余方式來保證 checkpoint block 的安全性,基于一個前提:last_checkpoint_lsn 不需要那么精確。

last_checkpoint_lsn 比實際需要應用 Redo 日志起點處的 lsn 小是沒關系的,不會造成數據頁不正確,只是會多掃描一點 Redo 日志而已,應用 Redo 日志時會過濾已經刷盤的臟頁對應的 Redo 日志。

(2)修復損壞的數據頁

把兩次寫文件中的所有數據頁都加載到內存緩沖區之后,需要用這些頁來把系統表空間、獨立表空間、undo 表空間中損壞的數據頁恢復到正常狀態。

正常狀態指的是 MySQL 崩潰之前,數據頁最后一次正確的刷新到磁盤的狀態。

恢復數據頁的過程是對兩次寫內存緩沖區中的所有數據頁進行循環,從兩次寫數據頁中讀取表空間 ID、頁號,然后根據表空間 ID 和頁號去系統表空間、獨立表空間、undo 表空間中讀取對應的數據頁。

讀取到對應的數據頁之后,會根據其 File Header、File Trailer 中的一些字段判斷數據頁是不是已經損壞了:

首先,從 File Header 中讀取 FILE_PAGE_LSN 字段,如果 FILE_PAGE_LSN 字段值大于當前系統已經生成的 Redo 日志的最大 LSN,說明數據庫出現了不可描述的錯誤,數據頁已經損壞。

然后,從 File Header 中讀取 FILE_PAGE_SPACE_OR_CHECKSUM 字段值,從 File Trailer 的前 4 字節中讀取 checksum。

如果 FILE_PAGE_SPACE_OR_CHECKSUM 字段值和 File Trailer checksum 不一樣,說明數據頁已經損壞。

一旦出現了上面 2 種情況中的 1 種,把兩次寫數據頁的內容復制到對應的數據頁中,數據頁就會恢復到正常狀態了。

(3)讀取 Redo 日志

前面確定了讀取 Redo 日志的起點 last_checkpoint_lsn,接下來就該讀取 Redo 日志了,主要流程如下:

圖片

?第 1 步,InnoDB 會以 64K? 為單位,從 Redo 日志文件讀取日志到 log buffer 中。

64K = 4 * innodb_page_size,所以,每次從 Redo 日志文件讀取的數據量取決于系統變量 innodb_page_size。

第 2 步,已經讀取到 log buffer 中的 block,利用 block header 和 block tailer 中的信息對 block 進行完整性檢驗之后,把 block body 信息拷貝到另一個緩沖區 parsing buffer。

parsing buffer 是一個 2M 的固定大小緩沖區,用于存放即將要被解析的 Redo 日志。

Redo 日志每個 block 的大小為 512 字節,block header 為 12 字節,block trailer 為 4 字節。

從 log buffer 的每個 block 中拷貝到 parsing buffer 的 block body 大小就是 512-12-4 = 496 字節,也就是每個 block 中存放的 Redo 日志數據部分。

第 3 步,解析 parsing buffer 中的 Redo 日志。

這一步解析 Redo 日志,實際上只是個預處理操作,并不會完整的解析每一條 Redo 日志,而是只會解析每一條 Redo 日志中的頭信息以及數據地址,包括以 4 個部分:

  • Redo 日志類型。
  • Redo 日志所屬數據頁的表空間 ID。
  • Redo 日志所屬數據頁的頁號。
  • Redo 日志數據,這部分只是得到了每一條 Redo 日志在 block body 中的地址,后面應用 Redo 日志到數據頁時會用到。

第 4 步,把第 3 步解析出來的每一條 Redo 日志的 4 個部分都拷貝到 hash 表中。

圖片

這個 hash 表是個嵌套結構,第 1 層 hash key 是表空間 ID,value 也是個 hash 結構,也就是第 2 層。

同一個表空間的 Redo 日志以頁單位組織到一起,存放到以表空間 ID 為 key 的第 1 層 hash value 中。

第 2 層的 hash key 是頁號,value 是需要應用到這個數據頁的 Redo 日志組成的鏈表。

同一個數據頁的 Redo 日志鏈表以頁號為 key,放在第 2 層 hash value 中。

鏈表中的 Redo 日志按照產生的先后順序排列,第 1 條就是要應用的這些 Redo 日志中最早產生的那條。

第 5 步,應用 Redo 日志到數據頁。

如果第 4 步進行的過程中,Redo 日志數據拷貝到 hash 表之后,導致 hash 表占用的空間大于 max_memory,那么需要應用 Redo 日志到數據頁,應用完成之后,清空 hash 表,為下一批 Redo 日志數據騰出空間。

這里的 max_memory 表示 hash 表能夠使用的最大內存空間。

1 ~ 5 步是個循環執行過程,經過 N 輪循環之后,hash 表中有非常大的可能性還存在著最后一批 Redo 日志,因為占用空間??小于等于?? max_memory 而只能在那里苦苦等待著被應用到 Redo 日志,這個工作就要等待第 6 步去干了。

第 6 步,收尾工作。

1 ~ 5 步循環結束之后,收尾工作就把 hash 表中剩下的 Redo 日志應用到數據頁,這是崩潰過程中最后一次應用 Redo 日志。

前面都沒有提到過存放 Redo 日志的 hash 表在哪里,能使用多大內存,不知道你有沒有好奇過?

這個 hash 表并不會單獨申請一大塊內存,而是借用了 buffer pool 中的內存。

因為在崩潰恢復過程中,進行到讀取 Redo 日志階段時,buffer pool 還沒有真正開始用,所以可以先借來給 hash 表用一下。

不過 hash 表并不能使用 buffer pool 的全部內存,而是需要保留一部分內存,用于應用 Redo 日志到數據頁的過程中,加載數據頁到 buffer pool 中。

保留內存大小為:buffer pool 實例數量 * 256 個數據頁?,buffer pool 中的剩余內存,就是第 5 步提到的 max_memory,也就是 hash 表能夠使用的最大內存。

(4)應用 Redo 日志

前面介紹讀取 Redo 日志,為了流程的完整性,有 2 個步驟已經涉及到應用 Redo 日志了。這里要介紹的是應用 Redo 日志的過程,會比上一小節深入一些。

讀取 Redo 日志階段,已經把所有需要應用的 Redo 日志都進行過預處理,并拷貝到 hash 表了。

存放 Redo 日志的 hash 表是一個嵌套結構:

  • 第 1 層的 hash key 是表空間 ID,hash value 還是一個 hash 表。
  • 第 2 層的 hash key 是頁號,hash value 是個 Redo 日志鏈表,鏈表中的每個元素就是一條需要應用的 Redo 日志,按照產生的先后排序。

把每個數據頁的 Redo 日志匯總到一起再去應用 Redo 日志,這樣做的好處是效率高。

在崩潰恢復過程中,每個數據頁只需要被加載到 buffer pool 中一次,一個數據頁的 Redo 日志能夠一次性應用,干脆利落。

應用 Redo 日志就是循環這個嵌套的 hash 表,把每一條 Redo 日志都應用到數據頁中,主要流程如下:

圖片

第 1 步,從第 1 層 hash 表中取到表空間 ID 和這個 undo 表空間下需要應用的 Redo 日志組成的第 2 層 hash 表。

第 2 步,從第 2 層 hash 表中取到一個頁號和該數據頁中需要應用的 Redo 日志鏈表。

第 3 步,判斷當前循環的數據頁是不是已經加載到 buffer pool 中了。

如果當前頁沒有加載到 buffer pool 中,進入第 4 步。

如果當前頁已經加載到 buffer pool 中,進入第 5 步。

第 4 步,把不在 buffer pool 中的數據頁加載到 buffer pool 中。

加載數據頁到 buffer pool 中,是一個異步的批量操作,有可能會一次加載多個數據頁。

也就是說,把數據頁從表空間加載到 buffer pool 中會觸發預讀,提前把一批需要應用 Redo 日志的數據頁一次性加載到 buffer pool 中。

預讀的數據頁,不是隨機讀取的,而是根據第 3 步判斷不在 buffer pool 中的數據頁的頁號(記為 page_no),計算出一個頁號范圍,把這個范圍內需要應用 Redo 日志的數據頁,全都加載到 buffer pool 中。

頁號范圍的起點:low_limit = page_no - page % 32,終點:low_limit + 32。

循環 low_limit ~ low_limit + 32 范圍內的頁號,只要碰到需要應用 Redo 日志的數據頁,就先把頁號臨時存放到一個數組里。

循環結束后,把數組里的頁號對應的數據頁異步批量加載到 buffer pool 中。

從上面的邏輯可以看到,一次預讀最多只讀 32 個數據頁。

第 5 步,應用 Redo 日志到數據頁。

根據第 1 步取到的表空間 ID和第 2 步取到的頁號,從 hash 表中獲取該數據頁需要應用的 Redo 日志鏈表。

從數據頁的 File Header 中讀取 FILE_PAGE_LSN,循環 Redo 日志鏈表中的每一條日志,判斷該日志的 start_lsn 是否大于等于 FILE_PAGE_LSN。

如果 start_lsn < FILE_PAGE_LSN,說明該 Redo 日志對應的操作修改的數據頁,在 MySQL 崩潰之前就已經刷盤,該 Redo 日志就不需要應用到數據頁了。

如果 start_lsn >= FILE_PAGE_LSN,說明該 Redo 日志需要應用到數據頁。

然后,根據 Redo 日志類型,調用不同的方法解析 Redo 日志,直接修改 buffer pool 中的數據頁,對該數據頁應用 Redo 日志的過程就完成了。

1 ~ 5 步是個循環過程,直到所有 undo 表空間的 Redo 日志都被應用到數據頁,循環過程結束。

4、刪除 undo 表空間

MySQL 運行過程中,如果有大事務往 undo 表空間中寫入大量 undo 日志,undo 表空間會變大。

在早期版本中,undo 表空間變大之后,就不能再縮回去了。

現在,如果系統變量 innodb_undo_log_truncate 設置為 on,當 undo 表空間增長到 innodb_max_undo_log_size 設置的大小(默認值為 1G)之后,InnoDB 會把這個 undo 表空間截斷為初始大小(16M)。

除了通過系統變量控制 undo 表空間自動截斷之外,還可以用下面這個 SQL 手動觸發:

ALTER UNDO TABLESPACE tablespace_name
SET INACTIVE

不管自動還是手動,有可能 InnoDB 正在進行 undo 表空間截斷操作,MySQL 就突然崩潰了,截斷表空間操作還沒有完成,那怎么辦?

等到下次啟動的時候,InnoDB 需要把未完成的 undo 表空間截斷操作繼續完成。

InnoDB 怎么知道哪些 undo 表空間的截斷操作沒有完成?

這就需要用到一個標記文件了,InnoDB 對某個 undo 表空間進行截斷操作之前,會創建一個對應的標記文件,文件名是這樣的:undo_表空間編號_trunc.log。

解釋一下表空間的兩個標識:表空間編號是給咱們人類看的,表空間 ID 是 MySQL 內部使用的,這兩者不一樣。

以 undo_001 表空間為例,表空間編號為 1?,InnoDB 對 undo_001 表空間進行截斷操作之前,會創建一個 undo_1_trunc.log 文件,如下:

[csch@csch /usr/local/mysql_8_0_29/data] ls -l | grep undo
-rw-r----- 1 csch staff 16M 8 27 12:04 undo_001
-rw-r----- 1 csch staff 16M 8 27 12:04 undo_002
-rw-r--r-- 1 csch staff 16K 6 22 12:36 undo_1_trunc.log

崩潰恢復過程中,InnoDB 如果發現某個表空間存在對應的 trunc.log 文件,說明這個 undo 表空間在 MySQL 崩潰時正在進行截斷操作。

但是,只通過 trunc.log 文件存在這一個條件,并不能確定 undo 表空間截斷操作沒有完成,還要進一步判斷。

接著讀取 trunc.log 文件的內容,把讀到的內容轉換成數字,判斷這個數字是不是等于 76845412。

76845412 是什么?稍候介紹。

如果等于,說明在 MySQL 崩潰之前,undo 表空間截斷操作已經完成,只是 trunc.log 文件還沒來得及刪除。此時,直接刪除這個文件就可以了。

如果不等于,說明 MySQL 崩潰時,undo 表空間截斷操作還沒有完成,那就需要繼續完成。此時,直接刪除 undo 表空間文件。

被刪除的 undo 表空間要等到初始化事務子系統之后,才會重建,重建過程我們稍后介紹。

舉個例子:啟動過程中發現了 undo_001 表空間對應的 trunc.log 文件,并且文件中存儲的數字不是 76845412,那就直接刪除 undo_001 表空間。

刪除之后,就只有 undo_1_trunc.log 文件能證明 undo_001 表空間存在過了,就像下面這樣:

[csch@csch /usr/local/mysql_8_0_29/data] ls -l | grep undo
-rw-r----- 1 csch staff 16M 8 27 12:04 undo_002
-rw-r--r-- 1 csch staff 16K 6 22 12:36 undo_1_trunc.log

為什么這里不把 undo 表空間對應的 trunc.log 文件一起刪除?

因為 undo 表空間要等到初始化事務子系統完成之后再重建,而 trunc.log 是 undo 表空間重建的憑證,所以,現在還不能刪除。

接下來我們再看看 trunc.log 文件的創建和寫入過程。

InnoDB 進行 undo 表空間截斷操作之前,就會創建 trunc.log 文件(大小為 innodb_page_size 字節),并把文件內容的所有字節都初始化為 NULL,然后開始進行 undo 表空間截斷操作。

操作完成之后,會往 trunc.log 文件中寫入一個被稱為魔數的數字:76845412,用于標識 undo 表空間截斷操作已經完成。

如果魔數成功寫入 trunc.log 文件,接下來會把 trunc.log 文件刪除,undo 表空間的截斷操作就結束了。

5、初始化事務子系統

現在,我們來到了初始化事務子系統階段。

InnoDB 之所以把初始化事務子系統安排在刪除 undo 表空間之后,有可能是為了避免讀取要被刪除的 undo 表空間,能夠節省一點點時間。

刪除還沒有完成截斷操作的 undo 表空間文件之后,剩下的 undo 表空間文件都需要讀取。

從 undo 表空間文件讀取未完成的事務,初始化事務子系統,主要過程如下:

初始化事務子系統還包含其它操作,不在本文介紹的范圍內。

圖片

第 1 步,從內存中的 undo 表空間對象數組中讀取 undo 表空間信息。

undo 表空間默認為 2 個,最多可以有 127 個。

有了獨立 undo 表空間之后,位于系統表空間中的回滾段就已經不再使用了,所以不需要從系統表空間的回滾段中讀取事務信息。

第 2 步,從 undo 表空間中頁號 = 3 的數據頁中讀取回滾段。

每個 undo 表空間可以有 1 ~ 128 個回滾段,由系統變量 innodb_rollback_segments 控制,默認值為 2.

第 3 步,從回滾段中讀取 undo slot。

回滾段的段頭頁中有 1024 個 undo slot(4 字節),每個 undo slot 對應一個 undo 段。

如果 undo slot 的值 等于 FIL_NULL,表示這個 undo slot 沒有關聯到 undo 段,繼續執行第 3 步,讀取下一個 undo slot。

如果 undo slot 的值 不等于 FIL_NULL,表示這個 undo slot 關聯了 undo 段,進入第 4 步。

第 4 步,從 undo slot 對應的 undo 段中讀取未完成事務的信息。

此時,undo slot 的值就是 undo 段的段頭頁的頁號,通過這個頁號可以讀取到 undo 段中的事務信息。

undo slot 關聯了 undo 段,說明數據庫崩潰時,undo 段中的事務還沒有完成,事務狀態可能是以下 3 種之一:

  • TRX_STATE_ACTIVE,表示事務還沒有進入提交階段。
  • TRX_STATE_PREPARED,表示事務已經提交了,但是只完成了二階段提交的 PREPARE 階段,還沒有完成 COMMIT 階段。
  • TRX_STATE_COMMITTED_IN_MEMORY,表示事務已經完成了二階段提交的 2 個階段,還剩一些收尾工作沒做,這種狀態的事務修改的數據已經可以被其它事務看見了。
  • 事務的收尾工作有哪些?清理已提交事務小節會介紹。

第 1 ~ 4 步是個循環的過程,直到讀完所有 undo 表空間中的事務信息結束。

6、重建 undo 表空間

對于存在 trunc.log 文件的 undo 表空間,因為之前 undo 表空間文件被刪除了,現在要開始著手重建 undo 表空間了,主要流程如下:

圖片

第 1 步,創建 trunc.log 文件,標記 undo 表空間重建操作正在進行中。

看到這里你可能會奇怪,undo 表空間對應的 trunc.log 文件不是沒有刪除嗎?這里為什么又要創建一次?

別急,且往下看。

在創建 undo 表空間對應的 trunc.log 文件之前,會先刪除之前舊的 trunc.log 文件,然后創建新的 trunc.log 文件。

新舊 trunc.log 文件名是一樣的,例如:對于 undo_001 表空間來說,新舊 trunc.log 文件名都是 undo_1_trunc.log。

?為什么要刪除舊的 trunc.log 文件再創建新的同名 trunc.log 文件呢?

因為重建? undo 表空間和新建 undo 表空間是同一套邏輯,而新建 undo 表空間之前,該表空間并不存在對應的 trunc.log 文件。

為了保持統一的邏輯,所以會先刪除已經存在的 trunc.log 文件。

第 2 步,創建 undo 表空間文件,初始大小為 16M,這個大小是硬編碼的。

第 3 步,初始化 undo 表空間,把表空間 ID、各種鏈表信息寫入表空間的 0 號頁中,然后分配一個新的數據頁,創建并初始化回滾段,回滾段數量由系統變量 innodb_rollback_segments 控制。

第 4 步,循環 undo 表空間中的所有回滾段,把每個回滾段中的 1024 個 undo slot 都初始化為 FIL_NULL。

第 5 步,標記 undo 表空間重建操作已經完成。

InnoDB 會先往 trunc.log 文件中寫入一個魔數 76845412,表示重建表空間操作已經完成。

寫入魔數成功之后,再把 trunc.log 文件刪除,重建一個 undo 表空間的過程就結束了。

如果有多個 undo 表空間需要重建,對于每個 undo 表空間都需要進行 1 ~ 5 步的流程。

7、處理事務

在初始化事務子系統小節,我們介紹過,從 undo 表空間中讀取出來的事務有 3 種狀態:

  • TRX_STATE_ACTIVE。
  • TRX_STATE_PREPARED。
  • TRX_STATE_COMMITTED_IN_MEMORY。

處理事務階段對這 3 種狀態會進行不同的處理,請接著往下看。

(1)清理已提交事務

這里要清理的已提交事務,指的是狀態為 TRX_STATE_COMMITTED_IN_MEMORY 的事務,包含 DDL 和 DML 事務。

這種狀態的事務已經完成二階段提交的 PREPARE 和 COMMIT 階段,是已經提交成功的事務,只差最后一點點清理工作,它們修改的數據已經能被其它事務看見了。

清理工作主要有幾點:

  • 處理 insert undo 段。如果 insert undo 段能被緩存,undo 段會被加入 insert_undo_cached 鏈表尾部,以備重復使用;如果 insert undo 段不能被緩存,undo 段就會被釋放。
  • 把事務從讀寫事務鏈表中刪除。
  • 把事務狀態修改為TRX_STATE_NOT_STARTED。

(2)回滾未提交 DDL 事務

未提交事務指的是狀態為 TRX_STATE_ACTIVE 的事務,也就是活躍事務。

崩潰恢復過程中,這種狀態的事務是需要直接回滾的。

你可能會有個疑問,DDL 事務不是不能回滾嗎?

DDL 事務不能回滾,這只是針對 MySQL 用戶而言,MySQL 內部并不會受到這個限制。

我們在使用 MySQL 的過程中,如果在一個 DML 事務中間執行了一條 DDL 語句,會觸發隱式提交,直接把 DML 事務提交了。

然后 DDL 會開啟一個新事務,這個新事務是自動提交的,DDL 執行完成之后,事務就直接提交了,我們是沒有機會對 DDL 事務進行回滾操作的。

MySQL 沒給我們回滾 DDL 事務的機會,但是它自己有這個特權。

(3)回滾未提交 DML 事務

未提交的 DDL 事務和 DML 事務在源碼中是在不同時間觸發的,它的回滾過程和 DDL 事務一樣。

事務回滾的過程比較復雜,本文我們就不展開說了,后續會寫一篇文章專門介紹事務回滾的過程。

(4)處理 PREPARE 事務

PREPARE 事務指的是狀態為 TRX_STATE_PREPARED 的事務,這種狀態的事務比較特殊,在崩潰恢復過程中,既有可能被提交,也有可能被回滾。

PREPARE 事務提交還是回滾,取決于這個事務的 XID 是否已經寫入到 binlog 日志文件中。

事務 XID 是以 binlog event 的方式寫入 binlog 日志文件的,event 的名字是 XID_EVENT。

一個事務只會有一個 XID,也就只會有一個 XID_EVENT 了。

要知道事務的 XID_EVENT 是否已經寫入到 binlog 日志文件,需要先讀取 binlog 日志文件。

從上面的介紹可以看到,處理 PREPARE 事務依賴于 binlog 日志文件,因此,這部分邏輯是在打開 binlog 日志文件的過程中實現的。

MySQL 在同一時刻只會往一個 binlog 日志文件中寫入  binlog event,在崩潰那一刻,承載寫入 event 的文件是最后一個 binlog 日志文件。

因此,崩潰恢復過程中,只需要掃描最后一個 binlog 日志文件,找到其中所有的 XID_EVENT, 用于判斷 PREPARE 事務的 XID_EVENT 是否已經寫入 binlog 日志文件。

如果 MySQL 上一次是正常關閉,啟動過程中,不會存在沒有完成的事務,沒有 PREPARE 事務需要處理,也就不用掃描最后一個 binlog 日志文件了。

MySQL 怎么知道上一次是不是正常關閉呢?

每個 binlog 日志文件的第 1 個 EVENT 都是 FORMAT_DESCRIPTION_EVENT,用于描述 binlog 日志文件格式信息,這個 EVENT 中包含一個標記 LOG_EVENT_BINLOG_IN_USE_F。

binlog 日志文件創建時,這個標記位會被設置為 1,表示 binlog 日志文件正在被使用。

LOG_EVENT_BINLOG_IN_USE_F 標記在 2 種情況下會被清除:

  • 切換 binlog 日志文件時,舊 binlog 日志文件的LOG_EVENT_BINLOG_IN_USE_F 標記會被清除。
  • MySQL 正常關閉時,正在使用的 binlog 日志文件的LOG_EVENT_BINLOG_IN_USE_F 標記會被清除。

如果 MySQL 突然崩潰,來不及把這個標記設置為 0。

那么下次啟動時,MySQL 讀取最后一個 binlog 日志文件的 FORMAT_DESCRIPTION_EVENT 發現 LOG_EVENT_BINLOG_IN_USE_F 標記為 1,就會進入處理 PREPARE 事務階段,主要流程如下:

圖片

第 1 步,掃描最后一個 binlog 日志文件,讀取 EVENT,找到其中所有的 XID_EVENT,并把讀取到的事務 XID 存放到一個集合中。

第 2 步,InnoDB 循環讀寫事務鏈表,每找到一個 PREPARE 事務都存放到數組中,最后把數組返回給 server 層。

第 3 步,讀取 InnoDB 返回的 PREPARE 事務數組,判斷事務 XID 是否在第 1 步的事務 XID 集合中。

第 4 步,提交或回滾事務。

如果事務 XID 在集合中,說明 MySQL 崩潰之前,事務 XID_EVENT 就已經寫入 binlog 日志文件了。

XID_EVENT 有可能已經同步給從服務器,從服務器上可能已經重放了這個事務。

這種情況下,為了保證主從數據的一致性,事務在主服務器上也需要提交。

如果事務 XID 不在集合中,說明 MySQL 崩潰之前,事務 XID_EVENT 沒有寫入 binlog 日志文件。

XID_EVENT 肯定也就沒有同步給從服務器了,同樣為了保證主從數據的一致性,事務在主服務器上也不能提交,而是需要回滾。

3 ~ 4 步是個循環過程,循環完 InnoDB 返回的 PREPARE 事務數組之后,處理 PREPARE 事務的過程結束,崩潰恢復主要流程也就完成了。

8、總結

MySQL 崩潰恢復過程的核心工作有 2 點:

  • 對于 MySQL 崩潰之前還沒有刷新到磁盤的數據頁(也就是臟頁),用 Redo 日志把這些數據頁恢復到 MySQL 崩潰之前那一刻的狀態,這相當于對臟頁進行一次刷盤操作。在這之前,需要用兩次寫緩沖區中的頁把損壞的數據頁修復為正常狀態,然后才能在此基礎上用 Redo 日志恢復數據頁。
  • 清理、提交、回滾還沒有完成的事務。


    對于已完成二階段提交的 PREPARE、COMMIT 2 個階段的事務,做收尾工作。


    對于活躍狀態的事務,直接回滾。


    對于 PREPARE 狀態的事務,如果事務 XID 已寫入 binlog 日志文件,提交事務,否則回滾事務。

本文轉載自微信公眾號「一樹一溪」,可以通過以下二維碼關注。轉載本文請聯系一樹一溪公眾號。

責任編輯:姜華 來源: 一樹一溪
相關推薦

2010-07-15 08:50:11

2021-01-26 13:47:08

MySQL存儲數據

2010-07-02 09:36:30

SQL Server

2015-07-15 14:38:38

HBase集群崩潰恢復Hadoop

2019-09-11 11:38:30

MySQLInnoDB數據庫

2019-09-11 09:37:17

數據庫MySQL系統

2017-09-27 13:42:42

數據庫MySQL斷電恢復

2011-08-15 09:19:22

2010-08-13 14:08:45

Oracle

2010-09-01 16:35:12

SQL刪除存儲過程

2018-07-24 15:26:01

蘋果MBP主板

2012-02-23 09:43:36

虛擬化VDIVHD備份

2009-01-22 10:18:39

2012-09-19 15:06:45

Lucene

2021-05-13 21:51:38

鴻蒙HarmonyOS應用

2021-11-23 21:21:07

線上排查服務

2014-03-26 11:40:49

金山毒霸系統崩潰

2018-03-06 09:54:48

數據庫備份恢復

2017-09-06 08:23:01

數據備份恢復過程正確姿勢

2009-11-20 13:29:59

Oracle數據庫恢復
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美一级二级三级视频 | 色必久久 | 中文字幕日韩在线 | 久久骚| 亚洲天堂av在线 | 日本大片在线播放 | 成年免费大片黄在线观看一级 | 久久91精品国产一区二区 | 欧美激情在线一区二区三区 | 亚洲视频在线观看一区二区三区 | 2018国产大陆天天弄 | 成人精品在线视频 | 久久综合久久自在自线精品自 | 亚洲欧美自拍偷拍视频 | 日韩精品一区二区三区第95 | 国产欧美精品一区二区 | 91精品国产一区二区三区 | 国产精品国产三级国产aⅴ原创 | 激情欧美一区二区三区中文字幕 | 久久99视频免费观看 | 久久99视频免费观看 | 俺去俺来也www色官网cms | www国产成人免费观看视频,深夜成人网 | 夜夜撸av| 欧美大片久久久 | 日韩免费一区二区 | 午夜电影日韩 | caoporn地址 | 成人午夜免费福利视频 | 久久精品色欧美aⅴ一区二区 | 免费精品久久久久久中文字幕 | 久久激情av | 成人免费av| 天堂一区在线 | 天天干天天爱天天 | 91在线网站| 中国毛片免费 | 成人欧美一区二区三区 | 久久国| 91美女在线观看 | 国产99热精品 |