1. 程式人生 > 實用技巧 >詳解:MySQL不會丟失資料的祕密

詳解:MySQL不會丟失資料的祕密

進入正題前先簡單看看MySQL的邏輯架構,相信我用的著。

MySQL邏輯架構

MySQL的邏輯架構大致可以分為三層:

  • 第一層:處理客戶端連線、授權認證,安全校驗等。
  • 第二層:伺服器server層,負責對SQL解釋、分析、優化、執行操作引擎等。
  • 第三層:儲存引擎,負責MySQL中資料的儲存和提取。

我們要知道MySQL的伺服器層是不管理事務的,事務是由儲存引擎實現的,而MySQL中支援事務的儲存引擎又屬InnoDB使用的最為廣泛,所以後續文中提到的儲存引擎都以InnoDB為主。

MySQL資料更新流程

記住!記住!記住!上邊這張圖,她是MySQL更新資料的基礎流程,其中包括redo log、bin log、undo log三種日誌間的大致關係,好了閒話少說直奔主題。

redo log(重做日誌)

redo log屬於MySQL儲存引擎InnoDB的事務日誌。

MySQL的資料是存放在磁碟中的,每次讀寫資料都需做磁碟IO操作,如果併發場景下效能就會很差。為此MySQL提供了一個優化手段,引入快取Buffer Pool。這個快取中包含了磁碟中部分資料頁(page)的對映,以此來緩解資料庫的磁碟壓力。

當從資料庫讀資料時,首先從快取中讀取,如果快取中沒有,則從磁碟讀取後放入快取;當向資料庫寫入資料時,先向快取寫入,此時快取中的資料頁資料變更,這個資料頁稱為髒頁,Buffer Pool中修改完資料後會按照設定的更新策略,定期刷到磁碟中,這個過程稱為刷髒頁

MySQL宕機

如果刷髒頁還未完成,可MySQL由於某些原因宕機重啟,此時Buffer Pool中修改的資料還沒有及時的刷到磁碟中,就會導致資料丟失,無法保證事務的永續性。

為了解決這個問題引入了redo log,redo Log如其名側重於重做!它記錄的是資料庫中每個頁的修改,而不是某一行或某幾行修改成怎樣,可以用來恢復提交後的物理資料頁,且只能恢復到最後一次提交的位置。

redo log用到了WAL(Write-Ahead Logging)技術,這個技術的核心就在於修改記錄前,一定要先寫日誌,並保證日誌先落盤,才能算事務提交完成。

有了redo log再修改資料時,InnoDB引擎會把更新記錄先寫在redo log中,在修改Buffer Pool中的資料,當提交事務時,呼叫fsync把redo log刷入磁碟。至於快取中更新的資料檔案何時刷入磁碟,則由後臺執行緒非同步處理。

注意:此時redo log的事務狀態是prepare,還未真正提交成功,要等bin log日誌寫入磁碟完成才會變更為commit,事務才算真正提交完成。

這樣一來即使刷髒頁之前MySQL意外宕機也沒關係,只要在重啟時解析redo log中的更改記錄進行重放,重新刷盤即可。

大小固定

redo log採用固定大小,迴圈寫入的格式,當redo log寫滿之後,重新從頭開始如此迴圈寫,形成一個環狀。

那為什麼要如此設計呢?

因為redo log記錄的是資料頁上的修改,如果Buffer Pool中資料頁已經刷磁碟後,那這些記錄就失效了,新日誌會將這些失效的記錄進行覆蓋擦除。

上圖中的write pos表示redo log當前記錄的日誌序列號LSN(log sequence number),寫入還未刷盤,迴圈往後遞增;check point表示redo log中的修改記錄已刷入磁碟後的LSN,迴圈往後遞增,這個LSN之前的資料已經全落盤。

write pos到check point之間的部分是redo log空餘的部分(綠色),用來記錄新的日誌;check point到write pos之間是redo log已經記錄的資料頁修改資料,此時資料頁還未刷回磁碟的部分。當write pos追上check point時,會先推動check point向前移動,空出位置(刷盤)再記錄新的日誌。

注意:redo log日誌滿了,在擦除之前,需要確保這些要被擦除記錄對應在記憶體中的資料頁都已經刷到磁碟中了。擦除舊記錄騰出新空間這段期間,是不能再接收新的更新請求的,此刻MySQL的效能會下降。所以在併發量大的情況下,合理調整redo log的檔案大小非常重要。

crash-safe

因為redo log的存在使得Innodb引擎具有了crash-safe的能力,即MySQL宕機重啟,系統會自動去檢查redo log,將修改還未寫入磁碟的資料從redo log恢復到MySQL中。

MySQL啟動時,不管上次是正常關閉還是異常關閉,總是會進行恢復操作。會先檢查資料頁中的LSN,如果這個 LSN 小於 redo log 中的LSN,即write pos位置,說明在redo log上記錄著資料頁上尚未完成的操作,接著就會從最近的一個check point出發,開始同步資料。

簡單理解,比如:redo log的LSN是500,資料頁的LSN是300,表明重啟前有部分資料未完全刷入到磁碟中,那麼系統則將redo log中LSN序號300到500的記錄進行重放刷盤。

undo log(回滾日誌)

undo log也是屬於MySQL儲存引擎InnoDB的事務日誌。

undo log屬於邏輯日誌,如其名主要起到回滾的作用,它是保證事務原子性的關鍵。記錄的是資料修改前的狀態,在資料修改的流程中,同時會記錄一條與當前操作相反的邏輯日誌到undo log中。

我們舉個栗子:假如更新ID=1記錄的name欄位,name原始資料為小富,現改name為程式設計師內點事

事務執行update X set name = 程式設計師內點事 where id =1語句時,先會在undo log中記錄一條相反邏輯的update X set name = 小富 where id =1記錄,這樣當某些原因導致服務異常事務失敗,就可以藉助undo log將資料回滾到事務執行前的狀態,保證事務的完整性。

那可能有人會問:同一個事物內的一條記錄被多次修改,那是不是每次都要把資料修改前的狀態都寫入undo log呢?

答案是不會的!

undo log只負責記錄事務開始前要修改資料的原始版本,當我們再次對這行資料進行修改,所產生的修改記錄會寫入到redo log,undo log負責完成回滾,redo log負責完成前滾。

回滾

未提交的事務,即事務未執行commit。但該事務內修改的髒頁中,可能有一部分髒塊已經刷盤。如果此時資料庫例項宕機重啟,就需要用回滾來將先前那部分已經刷盤的髒塊從磁碟上撤銷。

前滾

未完全提交的事務,即事務已經執行commit,但該事務內修改的髒頁中只有一部分資料被刷盤,另外一部分還在buffer pool快取上,如果此時資料庫例項宕機重啟,就需要用前滾來完成未完全提交的事務。將先前那部分由於宕機在記憶體上的未來得及刷盤資料,從redo log中恢復出來並刷入磁碟。

資料庫例項恢復時,先做前滾,後做回滾。

如果你仔細看過了上邊的MySQL資料更新流程圖就會發現,undo log、redo log、bin log三種日誌都是在刷髒頁之前就已經刷到磁碟了的,相互協作最大限度保證了使用者提交的資料不丟失。

bin log(歸檔日誌)

bin log是一種資料庫Server層(和什麼引擎無關),以二進位制形式儲存在磁碟中的邏輯日誌。bin log記錄了資料庫所有DDL和DML操作(不包含SELECT和SHOW等命令,因為這類操作對資料本身並沒有修改)。

預設情況下,二進位制日誌功能是關閉的。可以通過以下命令檢視二進位制日誌是否開啟:

mysql>SHOWVARIABLESLIKE'log_bin';
+---------------+-------+
|Variable_name|Value|
+---------------+-------+
|log_bin|OFF|
+---------------+-------+

bin log也被叫做歸檔日誌,因為它不會像redo log那樣迴圈寫擦除之前的記錄,而是會一直記錄日誌。一個bin log日誌檔案預設最大容量1G(也可以通過max_binlog_size引數修改),單個日誌超過最大值,則會新建立一個檔案繼續寫。

mysql>showbinarylogs;
+-----------------+-----------+
|Log_name|File_size|
+-----------------+-----------+
|mysq-bin.000001|8687|
|mysq-bin.000002|1445|
|mysq-bin.000003|3966|
|mysq-bin.000004|177|
|mysq-bin.000005|6405|
|mysq-bin.000006|177|
|mysq-bin.000007|154|
|mysq-bin.000008|154|

bin log日誌的內容格式其實就是執行SQL命令的反向邏輯,這點和undo log有點類似。一般來說開啟bin log都會給日誌檔案設定過期時間(expire_logs_days引數,預設永久儲存),要不然日誌的體量會非常龐大。

mysql>showvariableslike'expire_logs_days';
+------------------+-------+
|Variable_name|Value|
+------------------+-------+
|expire_logs_days|0|
+------------------+-------+
1rowinset

mysql>SETGLOBALexpire_logs_days=30;
QueryOK,0rowsaffected

bin log主要應用於MySQL主從模式(master-slave)中,主從節點間的資料同步;以及基於時間點的資料還原。

主從同步

通過下圖MySQL的主從複製過程,來了解下bin log在主從模式下的應用。

  • 使用者在主庫master執行DDL和DML操作,修改記錄順序寫入bin log;
  • 從庫slave的I/O執行緒連線上Master,並請求讀取指定位置position的日誌內容;
  • Master收到從庫slave請求後,將指定位置position之後的日誌內容,和主庫bin log檔案的名稱以及在日誌中的位置推送給從庫;
  • slave的I/O執行緒接收到資料後,將接收到的日誌內容依次寫入到relay log檔案最末端,並將讀取到的主庫bin log檔名和位置position記錄到master-info檔案中,以便在下一次讀取用;
  • slave的SQL執行緒檢測到relay log中內容更新後,讀取日誌並解析成可執行的SQL語句,這樣就實現了主從庫的資料一致;

基於時間點還原

我們看到bin log也可以做資料的恢復,而redo log也可以,那它們有什麼區別?

  • 層次不同:redo log 是InnoDB儲存引擎實現的,bin log 是MySQL的伺服器層實現的,但MySQL資料庫中的任何儲存引擎對於資料庫的更改都會產生bin log。
  • 作用不同:redo log 用於碰撞恢復(crash recovery),保證MySQL宕機也不會影響永續性;bin log 用於時間點恢復(point-in-time recovery),保證伺服器可以基於時間點恢復資料和主從複製。
  • 內容不同:redo log 是物理日誌,內容基於磁碟的頁Page;bin log的內容是二進位制,可以根據binlog_format引數自行設定。
  • 寫入方式不同:redo log 採用迴圈寫的方式記錄;binlog 通過追加的方式記錄,當檔案大小大於給定值後,後續的日誌會記錄到新的檔案上。
  • 刷盤時機不同:bin log在事務提交時寫入;redo log 在事務開始時即開始寫入。

bin log 與 redo log 功能並不衝突而是起到相輔相成的作用,需要二者同時記錄,才能保證當資料庫發生宕機重啟時,資料不會丟失。

relay log(中繼日誌)

relay log日誌檔案具有與bin log日誌檔案相同的格式,從上邊MySQL主從複製的流程可以看出,relay log起到一箇中轉的作用,slave先從主庫master讀取二進位制日誌資料,寫入從庫本地,後續再非同步由SQL執行緒讀取解析relay log為對應的SQL命令執行。

slow query log

慢查詢日誌(slow query log): 用來記錄在 MySQL 中執行時間超過指定時間的查詢語句,在 SQL 優化過程中會經常使用到。通過慢查詢日誌,我們可以查找出哪些查詢語句的執行效率低,耗時嚴重。

出於效能方面的考慮,一般只有在排查慢SQL、除錯引數時才會開啟,預設情況下,慢查詢日誌功能是關閉的。可以通過以下命令檢視是否開啟慢查詢日誌:

mysql>SHOWVARIABLESLIKE'slow_query%';
+---------------------+--------------------------------------------------------+
|Variable_name|Value|
+---------------------+--------------------------------------------------------+
|slow_query_log|OFF|
|slow_query_log_file|/usr/local/mysql/data/iZ2zebfzaequ90bdlz820sZ-slow.log|
+---------------------+--------------------------------------------------------+

通過如下命令開啟慢查詢日誌後,我發現iZ2zebfzaequ90bdlz820sZ-slow.log日誌檔案裡並沒有內容啊,可能因為我執行的 SQL 都比較簡單沒有超過指定時間。

mysql>SETGLOBALslow_query_log=ON;
QueryOK,0rowsaffected

上邊提到超過指定時間的查詢語句才算是慢查詢,那麼這個時間閾值又是多少嘞?我們通過long_query_time引數來檢視一下,發現預設是 10 秒。

mysql>SHOWVARIABLESLIKE'long_query_time';
+-----------------+-----------+
|Variable_name|Value|
+-----------------+-----------+
|long_query_time|10.000000|
+-----------------+-----------+

這裡我們將long_query_time引數改小為 0.001秒再次執行查詢SQL,看看慢查詢日誌裡是否有變化。

mysql>SETGLOBALlong_query_time=0.001;
QueryOK,0rowsaffected

果然再執行 SQL 的時,執行時間大於 0.001秒,發現慢查詢日誌開始記錄了。

慢查詢日誌

general query log

一般查詢日誌(general query log):用來記錄使用者的所有操作,包括客戶端何時連線了伺服器、客戶端傳送的所有SQL以及其他事件,比如MySQL服務啟動和關閉等等。MySQL伺服器會按照它接收到語句的先後順序寫入日誌檔案。

由於一般查詢日誌記錄的內容過於詳細,開啟後 Log 檔案的體量會非常龐大,所以出於對效能的考慮,預設情況下,該日誌功能是關閉的,通常會在排查故障需獲得詳細日誌的時候才會臨時開啟。

我們可以通過以下命令檢視一般查詢日誌是否開啟,命令如下:

mysql>showvariableslike'general_log';
+---------------+-------+
|Variable_name|Value|
+---------------+-------+
|general_log|OFF|
+---------------+-------+

下邊開啟一般查詢日誌並檢視日誌存放的位置。

mysql>SETGLOBALgeneral_log=on;
QueryOK,0rowsaffected
mysql>showvariableslike'general_log_file';
+------------------+---------------------------------------------------+
|Variable_name|Value|
+------------------+---------------------------------------------------+
|general_log_file|/usr/local/mysql/data/iZ2zebfzaequ90bdlz820sZ.log|
+------------------+---------------------------------------------------+

執行一條查詢 SQL 看看日誌內容的變化。

mysql>select*fromt_config;
+---------------------+------------+---------------------+---------------------+
|id|remark|create_time|last_modify_time|
+---------------------+------------+---------------------+---------------------+
|1325741604307734530|我是廣播表|2020-11-0918:06:44|2020-11-0918:06:44|
+---------------------+------------+---------------------+---------------------+

我們看到日誌內容詳細的記錄了所有執行的命令、SQL、SQL的解析過程、資料庫設定等等。

一般查詢日誌

error log

錯誤日誌(error log): 應該是 MySQL 中最好理解的一種日誌,主要記錄 MySQL 伺服器每次啟動和停止的時間以及診斷和出錯資訊。

預設情況下,該日誌功能是開啟的,通過如下命令查詢錯誤日誌檔案的存放路徑。

mysql>SHOWVARIABLESLIKE'log_error';
+---------------+----------------------------------------------------------------+
|Variable_name|Value|
+---------------+----------------------------------------------------------------+
|log_error|/usr/local/mysql/data/LAPTOP-UHQ6V8KP.err|
+---------------+----------------------------------------------------------------+

注意:錯誤日誌中記錄的可並非全是錯誤資訊,像 MySQL 如何啟動InnoDB的表空間檔案、如何初始化自己的儲存引擎,初始化buffer pool等等,這些也記錄在錯誤日誌檔案中。

總結

MySQL作為我們工作中最常接觸的中介軟體,熟練使用只算是入門,如果要在簡歷寫上一筆精通,還需要深入瞭解其內部工作原理,而這7種日誌也只是深入學習過程中的一個起點,學無止境,兄嘚幹就完了!

推薦閱讀

為什麼阿里巴巴的程式設計師成長速度這麼快

【手撕Spring原始碼合集】帶你從入門到精通;

《飛馬計劃》到底是什麼?可以讓數萬程式設計師為之著迷

看完三件事

如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:

點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。

關注公眾號 『 Java鬥帝 』,不定期分享原創知識。

同時可以期待後續文章ing