天天看點

【MySQL系列】- redo log知多少

前些天發現了一個巨牛的人工智能學習網站,通俗易懂,風趣幽默,忍不住分享一下給大家。​​點選跳轉到網站。​​

InnoDB 存儲引擎是以頁為機關來管理存儲空間的,我們的增删改查本質上都是對頁面上進行操作。我們知道在通路磁盤的時候,MySQL是會把資料加載到Buffer Pool然後進行操作的。對于DML操作,表、索引等的增删改DDL操作,還有資料本身是在Buffer Pool緩沖池中可能還沒來得及重新整理到磁盤中,系統或者伺服器突然崩潰,那這些資料該怎麼恢複呢?

redo log是什麼

官網定義:

A disk-based data structure used during crash recovery, to correct data written by incomplete transactions

翻譯過來就是,redo log是一種基于磁盤的資料結構,用于在故障恢複期間糾正由不完整事務寫入的資料。

redo log讓MySQL innodb引擎有奔潰恢複的能力。redo log是保證事務的完整性、持久性,隻有innodb引擎支援事務,是以redo log也是innodb引擎獨有的。

redo log 格式

redo 日志本質上隻是記錄了一下事務對資料庫做了哪些修改。 InnoDB 針對事務對資料庫的不同修改場景定義了多種類型的redo log,但是絕大部分類型的 redo log都有下面這種通用的結構:

【MySQL系列】- redo log知多少
  • type:redo 日志的類型,redo 日志設計大約有53種不同的類型日志。
  • space ID:表空間 ID
  • page number:頁号
  • data:該條 redo 日志的具體内容

mini-transaction

redo log是以組的形式寫入磁盤的。插入或者修改一條資料都會對B+樹進行修改,可能不止有一個修改點,甚至會涉及到頁分裂。為了保證這組資料的原子性,MySQL引入了mini-transaction(簡稱mtr)的概念。

mtr是當在 DML 操作期間在實體級别對内部資料結構進行更改時,InnoDB 處理的一個内部階段。mtr沒有復原的概念。一個所謂的mini-Transaction 可以包含一組 redo日志,在進行崩潰恢複時這一組redo 日志作為一個不可分割的整體。

一個事務可以包含若幹條語句,每一條語句其實是由若幹個 mini-Transaction組成,每一個 mini-Transaction 又可以包含若幹條 redo日志,最終形成了一個樹形結構。

LSN

LSN是log sequence number(日志序列号)的縮寫,用于記錄日志序号,它是一個不斷遞增的 unsigned long 類型的整數。LSN的初始值是8704,也就是說LSN從8704開始遞增。每一組由Mini-Transaction 生成的 redo log都有一個唯一的LSN值與

其對應,LSN 值越小,說明 redo 日志産生的越早。通過LSN,可以具體的定位到其在redo log檔案中的位置。

檢視系統中的LSN值

可以通過SQL語句檢視系統中LSN的值

SHOW ENGINE INNODB STATUS;      

在結果中找到如下片段

---
LOG
---
Log sequence number 1765718166
Log flushed up to   1765718166
Pages flushed up to 1765718166
Last checkpoint at  1765718157
0 pending log flushes, 0 pending chkp writes
212 log i/o's done, 0.00 log i/o's/second      
  • Log sequence number:系統中的 lsn 值,也就是目前系統已經寫入的 redo日志量,包括寫入log buffer中的日志。
  • Log flushed up to:flushed_to_disk_lsn 的值,也就是目前系統已經寫入磁盤的 redo 日志量。
  • Pages flushed up to:代表 flush連結清單中被最早修改的那個頁面對應的

    oldest_modification 屬性值。

  • Last checkpoint at:目前系統的checkpoint_lsn值

Log buffer

為了解決磁盤速度過慢的問題而引入了Buffer Pool。同理,

寫入 redo log時也不能直接直接寫到磁盤上,是以引入了Log buffer。Log buffer(日志緩沖區)是儲存要寫入磁盤上日志檔案的資料的記憶體區域,Log buffer可通過變量​​

​innodb_log_buffer_size​

​配置,預設是16M。Log buffer的資料會定期的重新整理到磁盤中,增加日志緩沖區大小可以支援大型事務,這樣無需在事務送出之前将redo log寫入磁盤,節省磁盤I/O,

InnoDB 為了更好的進行系統崩潰恢複,把通過Mini-Transaction 生成的redo日志都放在了大小為 512 位元組的塊(block)中,向log buffer中寫入 redo log的過程是順序的,也就是先寫入到block中,當該 block的空閑空間用完之後再往下一個 block中寫。Mini-Transaction運作過程中産生的一組 redo log,在Mini-Transaction結束時這組redo log會被複制到log buffer 中。

redo log 刷盤時機

前面說到log buffer中的資料會定時重新整理到磁盤,這就涉及到redo log 刷盤時機了。可能觸發刷盤的情況如下:

  1. log buffer空間不足時:log buffer 的大小是有限的,如果不停的往這個有限大小的 log buffer裡寫入日志,很快它就會被填滿。InnoDB認為如果目前寫入 log buffer 的 redo log量已經占滿了log buffer 總容量的大約一半左右,就需要把這些日志重新整理到磁盤上。
  2. 事務送出時:之是以使用redo log主要是因為它占用的空間少,還是順序寫,在事務送出時可以不把修改過的Buffer Pool 頁面重新整理到磁盤,但是為了保證持久性,必須要把修改這些頁面對應的 redo log重新整理到磁盤。

    變量​​

    ​innodb_flush_log_at_trx_commit​

    ​作用于事務送出時,innodb_flush_log_at_trx_commit 變量可配置3個值:
  • 當設定該值為1時,每次事務送出都要做一次刷盤,這是最安全的配置,即使當機也不會丢失事務;也是預設值
  • 當設定為2時,則在事務送出時隻做寫操作,隻保證寫到系統的page cache,是以執行個體崩潰不會丢失事務,但當機則可能丢失事務;
  • 當設定為0時,事務送出不會觸發redo寫操作,而是留給背景線程每秒一次的刷盤操作,是以執行個體崩潰将最多丢失1秒鐘内的事務。
【MySQL系列】- redo log知多少

顯然對性能的影響是随着持久化程度的增加而增加的。通常我們建議在日常場景将該值設定為1,但在系統高峰期臨時修改成2以應對大負載。

  1. 背景線程:預設每秒都會重新整理一次log buffer中的redo log到磁盤。可以通過變量​

    ​innodb_flush_log_at_timeout​

    ​來控制背景線程的重新整理頻率
  2. 正常關閉伺服器時等等

崩潰恢複

在伺服器不挂的情況下,redo 日志簡直就是個大累贅,不僅沒用,反而讓性能變得更差。但是萬一資料庫挂了,就可以在重新開機時根據redo日志中的記錄将頁面恢複到系統崩潰前的狀态。

MySQL可以根據redo log中的各種LSN值,來确定恢複的起點和終點。然後将 redo log中的資料,以哈希表的形式将一個頁面下的放到哈希表的一個槽中。之後就可以周遊哈希表,因為對同一個頁面進行修改的 redo log都放在了一個槽裡,是以可以一次性将一個頁面修複好(避免了很多讀取頁面的随機 IO)。并且通過各種機制,避免無謂的頁面修複,比如已經重新整理的頁面,進而提升崩潰恢複的速度。

在MySQL 8.0.21版本中,可以通過​

​ALTER INSTANCE DISABLE INNODB REDO_LOG​

​語句來關閉redo Log,但是最好還是不要關閉。

redo log 參數

MySQL的資料目錄下預設有兩個名為ib_logfile0和ib_logfile1 的檔案。可通過以下語句檢視資料目錄:

SHOW VARIABLES LIKE 'datadir'      

redo log的目錄和大小都是可修改的。

  • innodb_log_group_home_dir:修改redo log生成目錄,預設為目前資料目錄所在檔案夾下
  • innodb_log_file_size:單個redo log檔案的大小,預設是48M
  • innodb_log_files_in_group:redo log檔案組的檔案個數,預設是2,最大100

redo log在磁盤是以redo log檔案組的形式存在的,這些檔案的檔案名以ib_logfile開頭,以數字[N]結尾,N從0開始,N為正整數。

redo log在寫入檔案時,先寫入ib_logfile0,ib_logfile0寫滿了再寫ib_logfile1,以此類推往下寫,如果到最後一個檔案也寫滿了,就從ib_logfile0重新開始寫。

參考資料: