天天看點

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

作者:架構師成長曆程

任何項目都會有日志,MySQL也不例外,而且MySQL更是其中的佼佼者,日志種類繁多,而本篇的目的就是全解MySQL中的各類日志,如撤銷日志、錯誤日志、慢查詢日志、中繼日志、復原日志.....

其實日志的作用不言而喻,無論是線上排查,亦或是性能優化,幾乎都需要從日志中來獲得資訊作為依據,而MySQL中,很多很多的功能也都需要基于日志實作,比如事務復原、資料持久化、資料恢複、資料遷移、MVCC機制.....,是以本篇去闡述日志,也是為了友善撰寫後續的其他文章。

OK,廢話不多說了,咱們現在就開始吧~

一、Undo-log撤銷日志

Undo即撤銷的意思,但咱們通常也習慣稱它為復原日志,在日常開發過程中,如果代碼敲錯了,一般會習慣性的按下Ctrl+Z撤銷,而Undo-log的作用也是如此,但它是用來給MySQL撤銷SQL操作的。

在之前的《SQL執行篇》中曾聊到過,當一條寫入類型的SQL執行時,都會記錄Undo-log日志,會生成相應的反SQL放入到Undo-log中,例如:

  • 如果目前是insert插入操作,則生成一個對應的delete操作。
  • 如果目前是delete删除操作,InnoDB中會修改隐藏字段deleted_bit=1,則生成改為0的語句。
  • 如果目前的update修改操作,比如将姓名從竹子改成了熊貓,那就生成一個從熊貓改回竹子的操作。

當事務中某條SQL執行失敗時,MySQL就需要復原事務中其他執行成功的SQL,此時就會找到這個事務在Undo-log中生成的反SQL,然後将庫中的資料改回事務發生前的樣子。

大家看這段話,似乎沒有啥問題對不?也包括我之前的文章中也是這樣去描述的,但這裡其實存在些許誤導,在之前的《SQL執行篇》中,我們說一條寫SQL執行前,會生成對應的反SQL記錄在Undo-log中,但實際上并不會生成反SQL,這樣去叙述僅是為了友善大家了解罷了。

那咱們怎麼證明不會生成反SQL呢?如果大家有仔細研究過MySQL的日志,應該會發現Undo-log并不存在單獨的日志檔案,也就是磁盤中并不會存在xx-undo.log這類的檔案,那Undo-log存在哪兒呢?InnoDB預設是将Undo-log存儲在xx.ibdata共享表資料檔案當中,預設采用段的形式存儲。

也就是當一個事務嘗試寫某行表資料時,首先會将舊資料拷貝到xx.ibdata檔案中,将表中行資料的隐藏字段:roll_ptr復原指針會指向xx.ibdata檔案中的舊資料,然後再寫表上的資料。

那Undo-log究竟在xx.ibdata檔案中怎麼存儲呢?在共享表資料檔案中,有一塊區域名為Rollback Segment復原段,每個復原段中有1024個Undo-log Segment,每個Undo段可存儲一條舊資料,而執行寫SQL時,Undo-log就是寫入到這些段中。

不過在MySQL5.5版本前,預設隻有一個Rollback Segment,而在MySQL5.5版本後,預設有128個復原段,即支援128*1024條Undo記錄同時存在。

1.1、對于事務復原原理的糾正

結合上述糾正後的内容,咱們再對事務的復原原理稍作更正,實際上當一個事務需要復原時,本質上并不會以執行反SQL的模式還原資料,而是直接将roll_ptr復原指針指向的Undo記錄,從xx.ibdata共享表資料檔案中拷貝到xx.ibd表資料檔案,覆寫掉原本改動過的資料。

還是上個圖簡單了解一下吧,如下:

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

一條寫SQL執行的流程如上圖中的序号所示,當需要復原事務時,直接用Undo舊記錄覆寫表中修改過的新記錄即可!

如果是insert操作,由于插入之前這條資料都不存在,那麼就不會産生Undo記錄,此時復原時如何删除這條記錄呢?因為插入操作不會産生Undo舊記錄,是以隐藏字段中的roll_ptr=null,是以直接用null覆寫插入的新記錄即可,這樣也就實作了删除資料的效果~

1.2、基于Undo版本鍊實作MVCC

認真閱讀過《MySQL-MVCC機制原理剖析》的小夥伴對于這點并不陌生,Undo-log中記錄的舊資料并不僅僅隻有一條,一條相同的行資料可能存在多條不同版本的Undo記錄,内部會通過roll_ptr復原指針,組成一個單向連結清單,而這個連結清單則被稱之為Undo版本鍊,案例如下:

-- 事務T1:trx_id=1(兩次修改同一條資料)
UPDATE `zz_users` SET user_name = "竹子" WHERE user_id = 1;
UPDATE `zz_users` SET user_sex = "男" WHERE user_id = 1;
複制代碼           

Undo-log中的舊資料版本鍊示意圖大緻如下:

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

當然,對于如何利用版本鍊實作MVCC機制,這點就不反複贅述了,沒了解過的可以去看關于MVCC原理剖析的那篇文章。

1.3、Undo-log的記憶體緩沖區

InnoDB在MySQL啟動時,會在記憶體中建構一個BufferPool,而這個緩沖池主要存放兩類東西,一類是資料相關的緩沖,如索引、鎖、表資料等,另一類則是各種日志的緩沖,如Undo、Bin、Redo....等日志。

而當一條寫SQL執行時,不會直接去往磁盤中的xx.ibdata檔案寫資料,而是會寫在undo_log_buffer緩沖區中,因為工作線程直接去寫磁盤太影響效率了,寫進緩沖區後會由背景線程去刷寫磁盤。

OK~,那麼如果當一個事務送出時,Undo的舊記錄會不會立馬被删除呢?因為事務都送出了,不需要再復原改動過的資料,似乎用不上Undo舊記錄了,對嗎?确實如此,但不會立馬删除Undo記錄,對于舊記錄的删除工作,InnoDB中會有專門的purger線程負責,purger線程内部會維護一個ReadView,它會以此作為判斷依據,來決定何時移除Undo記錄。

為什麼不是事務送出後立馬删除Undo記錄呢?因為可能會有其他事務在通過快照,讀Undo版本鍊中的舊資料,直接移除可能會導緻其他事務讀不到資料,是以删除的工作就交給了purger線程。

1.4、Undo-log相關的參數

最後再來看看關于Undo-log的一些參數,其實在MySQL5.5之前沒有太多參數,如下:

  • innodb_max_undo_log_size:本地磁盤檔案中,Undo-log的最大值,預設1GB。
  • innodb_rollback_segments:指定復原段的數量,預設為1個。

除開上述兩個參數外,其他參數基本上是在MySQL5.6才有的,如下:

  • innodb_undo_directory:指定Undo-log的存放目錄,預設放在.ibdata檔案中。
  • innodb_undo_logs:指定復原段的數量,預設為128個,也就是之前的innodb_rollback_segments。
  • innodb_undo_tablespaces:指定Undo-log分成幾個檔案來存儲,必須開啟innodb_undo_directory參數。
  • innodb_undo_log_truncate:是否開啟Undo-log的線上壓縮功能,即日志檔案超過大小一半時自動壓縮,預設OFF關閉。

沒錯,在MySQL5.5版本以後,Undo-log日志支援單獨存放,并且多出了幾個參數可以調整Undo-log的區域。

二、Redo-log重做日志

詳細聊明白了Undo-log後,緊接着再來看看它的同胞兄弟:Redo-log日志,為啥說它兩是同胞兄弟呢?因為這兩日志都是InnoDB引擎獨有的,Undo-log主要用于實作事務復原和MVCC機制,而Redo-log則用來實作資料的恢複。還記得在《MySQL事務篇》中聊到的資料恢複機制嘛?

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

Redo-log日志的作用就在于此,下面詳細的聊一下它。

2.1、為何需要Redo-log日志?

衆所周知的一點:MySQL絕大部分引擎都是是基于磁盤存儲資料的,但如若每次讀寫資料都走磁盤,其效率必然十分低下,是以InnoDB引擎在設計時,當MySQL啟動後就會在記憶體中建立一個BufferPool,運作過程中會将大量操作彙集在記憶體中進行,比如寫入資料時,先寫到記憶體中,然後由背景線程再刷寫到磁盤。

雖然使用BufferPool提升了MySQL整體的讀寫性能,但它是基于記憶體的,也就意味着随着機器的當機、重新開機,其中儲存的資料會消失,那當一個事務向記憶體中寫入資料後,MySQL突然當機了,豈不代表這條未刷寫到磁盤的資料會丢失嗎?答案是Yes,也正由于該原因,Redo-log應運而生!

因為資料寫到記憶體後有丢失風險,這明顯違背了事務ACID原則中的持久性,是以Redo-log的出現就是為了解決該問題,Redo-log是一種預寫式日志,即在向記憶體寫入資料前,會先寫日志,當後續資料未被刷寫到磁盤、MySQL崩潰時,就可以通過日志來恢複資料,確定所有送出的事務都會被持久化。

但是要注意:工作線程執行SQL前,寫的Redo-log日志,也是寫在了記憶體中的redo_log_buffer緩沖區。

既然Redo-log日志也是先寫記憶體,那Redo-log有沒有丢失的風險呢?這跟Redo-log的刷盤政策有關。

2.2、Redo-log的刷盤政策

對于記憶體中的redo_log_buffer緩沖區,其中寫入的資料會何時被刷寫到磁盤?對于這點在之前《SQL執行篇-寫SQL執行時的日志操作》中簡單的提到過:

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

簡單來說就是刷盤的時機由innodb_flush_log_at_trx_commit參數來控制,預設是處于第二個級别,也就是每次送出事務時都會刷盤,這也就意味着一個事務執行成功後,相應的Redo-log日志絕對會被刷寫到磁盤中,是以無需擔心會出現丢失風險。

那如果事務還未送出時,MySQL當機怎麼辦?對于這個問題在事務篇的那個截圖中有,不再反複贅述!

但再來思考一個問題:既然Redo-log要寫磁盤,那為何不在寫日志的時候,直接把資料寫到磁盤裡面去呢?

2.3、Redo-log中為何“多此一舉”?

先刷寫一次Redo-log日志到磁盤,背景線程再根據Redo-log日志把資料落盤,這個動作似乎看起來有些多餘對吧?但實際上這樣做好處很大:

  • ①日志比資料先落入磁盤,是以就算MySQL崩潰也可以通過日志恢複資料。
  • ②寫日志時是以追加形式寫到末尾,而寫資料時則是計算資料位置,随機插入。

對于第一點好處就不多說了,重點來聊一聊第二點,因為寫日志的時候,隻需要将記錄追加到日志檔案的尾部即可,這是按順序寫入,但寫入表資料時,還需要先先計算資料的位置,比如修改一條資料時,需要先判斷這條資料在磁盤檔案中的那個位置,找到了位置再寫入,這是随機寫入,順序寫入的速度會比随機寫入快很多很多。

因為寫日志會比寫資料落盤快,是以日志落盤後傳回,比資料落盤後傳回要快,對于用戶端而言,響應時間會更短~

2.4、Redo-log相關的參數

這裡也列舉出幾個Redo-log日志中,較為重要的系統參數:

  • innodb_flush_log_at_trx_commit:設定redo_log_buffer的刷盤政策,預設每次送出事務都刷盤。
  • innodb_log_group_home_dir:指定redo-log日志檔案的儲存路徑,預設為./。
  • innodb_log_buffer_size:指定redo_log_buffer緩沖區的大小,預設為16MB。
  • innodb_log_files_in_group:指定redo日志的磁盤檔案個數,預設為2個。
  • innodb_log_file_size:指定redo日志的每個磁盤檔案的大小限制,預設為48MB。

其中主要講一下Redo-log的本地磁盤檔案個數,為啥預設是兩個呢?因為MySQL通過來回寫這兩個檔案的形式記錄Redo-log日志,用兩個日志檔案組成一個“環形”,如下:

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

先來簡單解釋一下圖中存在的兩根指針:

  • write pos:這根指針用來表示目前Redo-log檔案寫到了哪個位置。
  • check point:這根指針表示目前哪些Redo-log記錄已經失效且可以被擦除(覆寫)。

兩根指針中間區域,也就是圖中的紅色區域,代表是可以寫入日志記錄的可用空間,而藍色區域則表示日志落盤但資料還未落盤的記錄,這句話怎麼了解呢?

當一個事務寫了redo-log日志、并将資料寫入緩沖區後,但資料還未寫到本地的表資料檔案中,此時這個事務對應的redo-log記錄就為上圖中的藍色,而當一個事務所寫的資料也落盤後,對應的redo-log記錄就會變為紅色。

當write pos指針追上check point指針時,紅色區域就會消失,也就代表Redo-log檔案滿了,再當MySQL執行寫操作時就會被阻塞,因為無法再寫入redo-log日志了,是以會觸發checkpoint刷盤機制,将redo-log記錄對應的事務資料,全部刷寫到磁盤中的表資料檔案後,阻塞的寫事務才能繼續執行。

觸發checkpoint刷盤機制後,随着資料的落盤,check point指針也會不斷的向後移動,紅色區域也會不斷增長,是以阻塞的寫事務才能繼續執行。

OK~,再補齊一些關于checkpoint機制的系統參數:

  • innodb_log_write_ahead_size:設定checkpoint刷盤機制每次落盤動作的大小,預設為8K,如果你要設定,必須要為4k的整數倍,這跟read-on-write問題有關,具體的可以參考:《這篇文章》。
  • innodb_log_compressed_pages:是否對Redo日志開啟頁壓縮機制,預設ON,這跟InnoDB的頁壓縮技術有關,後續《特性篇》聊。
  • innodb_log_checksums:Redo日志完整性效驗機制,預設開啟,必須要開啟,否則有可能刷寫資料時,隻刷一半,出現類似于“網絡粘包”的問題。

後續幾個參數略微有些複雜,因為主要跟MySQL5.6之後的優化有關,後續在《MySQL特性篇》中會再次細聊。

三、Bin-log變更日志

Bin-log日志也被稱之為二進制日志,作用與Redo-log類似,主要是記錄所有對資料庫表結構變更和表資料修改的操作,對于select、show這類讀操作并不會記錄。bin-log是MySQL-Server級别的日志,也就是所有引擎都能用的日志,而redo-log、undo-log都是InnoDB引擎專享的,無法跨引擎生效。

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

OK~,再看到這張寫SQL的執行流程圖,重點觀察裡面的第⑨步,無論目前表使用的是什麼引擎,實際上都需要完成記錄bin-log日志這步操作,和之前分析的兩種日志相同,bin-log也由記憶體日志緩沖區+本地磁盤檔案兩部分組成,這也就意味着:寫bin-log日志時,也會先寫緩沖區,然後由背景線程去刷盤。

3.1、bin-log的緩沖區

為啥要單獨把bin-log的緩沖區拎出來講呢?因為它跟redo-log、undo-log的緩沖區并不同,前面分析的兩種日志緩沖區,都位于InnoDB建立的共享BufferPool中,而bin_log_buffer是位于每條線程中的,關系圖如下:

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

也就是說,MySQL-Server會給每一條工作線程,都配置設定一個bin_log_buffer,而并不是放在共享緩沖區中,這是為啥呢?因為MySQL設計時要相容所有引擎,直接将bin-log的緩沖區,設計線上程的工作記憶體中,這樣就能夠讓所有引擎通用,并且不同線程/事務之間,由于寫的都是自己工作記憶體中的bin-log緩沖,是以并發執行時也不會沖突!

bin_log_buffer的設計,就類似于咱們之前講《并發程式設計》時講過的《ThreadLocal線程變量副本》。

OK~,簡單了解bin-log緩沖區的設計後,對于bin-log的刷盤政策就不反複贅述了,就是通過sync_binlog參數控制,與之前redo-log類似(上面有)。

3.2、Bin-log本地日志檔案的格式

bin-log的本地日志檔案,采用的是追加寫的模式,也就是一直向檔案末尾寫入新的日志記錄,當一個日志檔案寫滿後,會建立一個新的bin-log日志檔案,每個日志檔案的命名為mysql-bin.000001、mysql-bin.000002、mysql-bin.00000x....,可以通過show binary logs;指令檢視已有的bin-log日志檔案。

接着再來聊聊bin-log檔案的内部格式~

在bin-log的本地檔案中,其中存儲的日志記錄共有Statment、Row、Mixed三種格式,分别是啥意思呢?

Statment:每一條會對資料庫産生變更的SQL語句都會記錄到bin-log中。

咋了解這句話呢?舉個例子:

-- 查詢一次使用者表資料,如下:
SELECT * FROM `zz_users`;
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time       |
+---------+-----------+----------+----------+---------------------+
|       1 | 熊貓      | 女       | 6666     | 2022-08-14 15:22:01 |
|       2 | 竹子      | 男       | 1234     | 2022-09-14 16:17:44 |
|       3 | 子竹      | 男       | 4321     | 2022-09-16 07:42:21 |
|       4 | 貓熊      | 女       | 8888     | 2022-09-27 17:22:59 |
|       9 | 黑竹      | 男       | 9999     | 2022-09-28 22:31:44 |
+---------+-----------+----------+----------+---------------------+

-- 将使用者表中所有 ID>3的密碼重置
update `zz_users` set `password` = "1111" where user_id > 3;
複制代碼           

比如上述這個事務執行時,MySQL會将第二條update語句記錄在bin-log日志中,但對于select語句則不會記錄(在記錄SQL時,還會記錄一下SQL的上下文資訊,如執行時間、事務ID、日志量......)。

這種方式的優勢很明顯,由于隻記錄對資料庫産生變更操作的SQL,是以不會産生太大的日志量,節約空間,恢複資料時因為資料量小,是以磁盤IO次數少,是以性能會比較不錯。同時做主備等高可用架構時,資料同步也會較小,是以比較節省帶寬。

但雖然優勢不小,但缺點頁很明顯,即恢複資料、主從同步資料時,有時會出現資料不一緻的情況,如SQL中使用了sysdate()、now()這類函數,比如舉個簡單的例子:
insert into `zz_users` values(11,"棕熊","男","3333",sysdate());
複制代碼           

比如這條插入語句,由于對使用者表産生了變更操作,是以會被記錄到bin-log中,但當主從架構之間做資料同步時,假設将這條SQL同步到從機上執行,此時問題就來了,sysdate()函數會擷取機器的目前時間,但主機和從機執行這條SQL顯然不是同一時間,是以就會導緻ID=11的這條資料,在主機和從機的使用者表中,注冊時間會出現不一緻。

Row:這種模式就是為了解決Statment模式的缺陷,Row模式中不再記錄每條造成變更的SQL語句,而是記錄具體哪一個分區中的、哪一個頁中的、哪一行資料被修改了。

這又怎麼了解呢?還是以前面的重置密碼的例子來說:

-- 将使用者表中所有 ID>3的密碼重置(ID=4、9的兩條資料會被重置)
update `zz_users` set `password` = "1111" where user_id > 3;
複制代碼           

在這種模式下,就不會記錄這條update語句,而是記錄發生改變的行資料,即ID=4、9的兩條使用者資料,會将其更改後的值記錄到bin-log日志中。

這種方式因為不記錄SQL,而是記錄修改後的值,是以有個很大的好處是:當主從同步資料時,複制的是主機上的資料,是以不會出現主從資料不一緻的情況。但缺陷同樣很明顯,比如表中有800W資料,現在我對ID<600W的所有資料進行了修改操作,哪也就意味着會有600W條記錄寫入bin-log日志,這個資料量可想而知,其磁盤IO、網絡帶寬開銷會很高。

Mixed:這種被稱為混合模式,即Statment、Row的結合版,因為Statment模式會導緻資料出現不一緻,而Row模式資料量又會很大,是以Mixed模式結合了兩者的優劣勢,對于可以複制的SQL采用Statment模式記錄,對于無法複制的SQL采用Row記錄。

這樣即保留了Statment模式的資料量小,又具備Row模式的資料精準性,魚和熊掌兼得焉~

其實看到這裡,如果比較熟悉Redis4.x版本的小夥伴應該會有種熟悉感,Redis的RDB、AOF持久化模式,正好對應MySQL的Statment、Row模式,而Redis4.0引入了混合持久化機制,MySQL5.1版本也引入了混合日志模式~

3.2、為什麼有了Redo-log還需要Bin-log?

Redo-log、Bin-log都是記錄更新資料庫的操作,但為什麼會同時設計兩個呢?這其實跟InnoDB有關,如若對MySQL舊史較為熟悉的小夥伴應該知道,MySQL自己的官方引擎實際上最初是MyISAM,InnoDB是Innobase-Oy公司開發的一款可拔插式引擎,由于InnoDB被MySQL支援後使用頻率越來越高,後面MySQL官方才用InnoDB替換了MyISAM作為預設引擎。

那這跟上面的問題有啥關系呢?其實關系很大,MySQL-Server、MyISAM是出自于官方的産品,是以MyISAM中并未設計記錄變更操作的日志,記錄變更操作由MySQL-Server來通過Bin-log完成。

但因為MyISAM不支援事務,是以MySQL-Server設計的Bin-log無法用于災難恢複,是以InnoDB在設計時,又重新設計出Redo-log日志,可以利用該日志實作crash-safe災難恢複能力,確定任何事務送出後資料都不會丢失。

3.3、Redo-log、Bin-log兩者的差別

對于Redo-log、Bin-log兩者的差別,主要可以從四個次元上來說:

  • ①生效範圍不同,Redo-log是InnoDB專享的,Bin-log是所有引擎通用的。
  • ②寫入方式不同,Redo-log是用兩個檔案循環寫,而Bin-log是不斷建立新檔案追加寫。
  • ③檔案格式不同,Redo-log中記錄的都是變更後的資料,而Bin-log會記錄變更SQL語句。
  • ④使用場景不同,Redo-log主要實作故障情況下的資料恢複,Bin-log則用于資料災備、同步。

3.4、不小心删庫後應該跑路嗎?

首先來說一下,為啥要讨論這個問題呢,這是由于之前《MySQL架構篇》的評論區的一位小夥伴提出的:

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

這裡有兩個問題:①删庫後跑路會不會被人發現?②MySQL能不能和Oracle一樣具備閃回功能?

先來簡單聊聊第一個問題,如果你線上上真的删庫了,哪就先别想着跑路,你跑不掉!因為bin-log日志中會記錄執行SQL的連接配接會話資訊,同時一般規模較大的企業,都會搭建完善的監控系統,會監控服務的網絡連接配接,是以當你删庫後,可以順着bin-log → session → network-connection這條線确定執行删庫SQL的IP!如果你還未斷開連接配接,直接通過MySQL的指令就能定位到删庫的IP,是以基本上删庫了,是可以定位到責任人的!

當然,如果項目配備的監控系統不夠完善,同時你的連接配接已經斷開,并且電腦換了一個區域網路,同時時間來到了三天以後,如果還沒人發現你,哪基本上跑路也不會有人發現,但這樣幹,會存在些許做賊心虛的嫌疑~

OK~,不過多的讨論這個話題了,總之你跑路肯定不能跑,誤删了資料就要想辦法恢複,咋恢複呢?通過日志恢複,但Redo-log、Bin-log都會記錄資料庫的變更操作,是以用誰比較合适呢?

答案是Bin-log,因為Redo-log采用循環寫的方式,一邊寫會一邊擦,裡面無法得到完整的資料,而Bin-log是追加寫的模式,你不去主動删除磁盤的日志檔案,并且磁盤的空間還足夠,一般Bin-log日志檔案都會在本地,是以當你删庫後,可以直接去本地找Bin-log的日志檔案,然後拷貝出來一份,再打開最後一個檔案,把裡面删庫的記錄手動移除,再利用mysqlbinlog工具導出xx.SQL檔案,最後執行該SQL檔案即可恢複删庫前的資料。

這裡就叙說大體邏輯,具體的資料恢複操作,會在後續的《MySQL線上排查與資料恢複篇》細講,其實也可以通過Flashback工具提供的閃回功能恢複資料,但以後再細聊~

3.5、bin-log相關的參數

  • log_bin:是否開啟bin-log日志,預設ON開啟,表示會記錄變更DB的操作。
  • log_bin_basename:設定bin-log日志的存儲目錄和檔案名字首,預設為./bin.0000x。
  • log_bin_index:設定bin-log索引檔案的存儲位置,因為本地有多個日志檔案,需要用索引來确定目前該操作的日志檔案。
  • binlog_format:指定bin-log日志記錄的存儲方式,可選Statment、Row、Mixed。
  • max_binlog_size:設定bin-log本地單個檔案的最大限制,最多隻能調整到1GB。
  • binlog_cache_size:設定為每條線程的工作記憶體,配置設定多大的bin-log緩沖區。
  • sync_binlog:控制bin-log日志的刷盤頻率。
  • binlog_do_db:設定後,隻會收集指定庫的bin-log日志,預設所有庫都會記錄。
  • ......:省略其他不常用參數。

3.6、Redo-log的兩階段送出

詳細大家應該聽說過MySQL事務兩階段送出方案,啥叫做事務兩階段送出呢?實則是指Redo-log分兩次寫入,如下:

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

注意看之前給出的寫SQL執行流程圖,其中第⑤、⑩步,分别會寫兩次Redo-log日志,這個日志的作用前面講的很明白了,主要用來做崩潰恢複,但為什麼要分兩次寫呢?寫一次不行嘛?

其實想要弄明白這個問題,要結合bin-log日志一起來聊。

如果隻寫一次的話,那到底先寫bin-log還是redo-log呢?

先寫bin-log,再寫redo-log:當事務送出後,先寫bin-log成功,結果在寫redo-log時斷電當機了,再重新開機後由于redo-log中沒有該事務的日志記錄,是以不會恢複該事務送出的資料。但要注意,主從架構中同步資料是使用bin-log來實作的,而當機前bin-log寫入成功了,就代表這個事務送出的資料會被同步到從機,也就意味着從機會比主機多出一條資料。
先寫redo-log,再寫bin-log:當事務送出後,先寫redo-log成功,但在寫bin-log時當機了,主節點重新開機後,會根據redo-log恢複資料,但從機依舊是依賴bin-log來同步資料的,是以從機無法将這個事務送出的資料同步過去,畢竟bin-log中沒有撒,最終從機會比主機少一條資料。

經過上述分析後可得知:如果redo-log隻寫一次,那不管誰先寫,都有可能造成主從同步資料時的不一緻問題出現,為了解決該問題,redo-log就被設計成了兩階段送出模式,設定成兩階段送出後,整個執行過程有三處崩潰點:

  • redo-log(prepare):在寫入準備狀态的redo記錄時當機,事務還未送出,不會影響一緻性。
  • bin-log:在寫bin記錄時崩潰,重新開機後會根據redo記錄中的事務ID,復原前面已寫入的資料。
  • redo-log(commit):在bin-log寫入成功後,寫redo(commit)記錄時崩潰,因為bin-log中已經寫入成功了,是以從機也可以同步資料,是以重新開機時直接再次送出事務,寫入一條redo(commit)記錄即可。

通過這種兩階段送出的方案,就能夠確定redo-log、bin-log兩者的日志資料是相同的,bin-log中有的主機再恢複,如果bin-log沒有則直接復原主機上寫入的資料,確定整個資料庫系統的資料一緻性。

OK~,最後再簡單補充一點:為什麼bin-log又被叫做二進制日志呢?因為記錄日志時,MySQL寫入的是二進制資料,而并非字元資料,也就意味着直接用cat/vim這類工具是無法打開的,必須要通過MySQL提供的mysqlbinlog工具解析檢視。

四、Error-log錯誤日志

前面已經将最重要的undo-log、redo-log、bin-log三大日志講明白了,這三個日志都是用來輔助MySQL、InnoDB線上上正常運作的,但凡其中一個出現問題,都有可能導緻MySQL無法正常工作。

接下來再看幾個輔助性的日志,即error-log、slow-log、relay-log。
  • error-log:MySQL線上MySQL由于非外在因素(斷電、硬體損壞...)導緻崩潰時,輔助線上排錯的日志。
  • slow-log:系統響應緩慢時,用于定位問題SQL的日志,其中記錄了查詢時間較長的SQL。
  • relay-log:搭建MySQL高可用熱備架構時,用于同步資料的輔助日志。

接下來先看error-log,這個日志的作用很明顯,從名字都能得知它是用于記錄MySQL報錯資訊的,其中涵蓋了MySQL-Server的啟動、停止運作的時間,以及報錯的診斷資訊,也包括了錯誤、警告和提示等多個級别的日志詳情。

通過錯誤日志,一方面可以用來監控MySQL的運作狀态,便于預防故障、發現故障,同時也可以在出現問題時,用來輔助排查問題、修複故障,因為MySQL-Server的錯誤日志是預設開啟的,并且無法手動關閉!

一般來說,error-log日志檔案預設是在MySQL安裝目錄下的data檔案夾中,但如果你想要改變位置,哪也可以通過log-error這個參數,來手動指定儲存的位置與檔案名。

如果你不清楚錯誤日志的位置,也可以通過SHOW VARIABLES LIKE 'log_error';指令來檢視。

最後稍微提一嘴,如何根據錯誤日志來排錯問題呢?實際上非常簡單,在MySQL故障的情況下,打開error-log檔案,然後搜尋Error、Waiting級别的日志記錄,然後參考診斷資訊即可。

五、Slow-log慢查詢日志

對于線上響應緩慢的問題,一步步的排查過程之後還未找到問題,最終就會來到資料庫,嘗試對SQL或索引調優,但一個項目中,存在成千上萬條SQL,到底是由于哪條SQL造成的響應緩慢,如果一條條去分析,其工作量定然非常吃力,為了排查問題時足夠輕松,MySQL官方支援開啟慢查詢日志。

慢查詢日志是什麼呢?也就是當一條SQL執行的時間超過規定的門檻值後,那麼這些耗時的SQL就會被記錄在慢查詢日志中,當線下出現響應緩慢的問題時,可以直接通過檢視慢查詢日志定位問題,定位到産生問題的SQL後,再用explain這類工具去生成SQL的執行計劃,然後根據生成的執行計劃來判斷為什麼耗時長,是由于沒走索引,還是索引失效等情況導緻的。

不過對于慢查詢SQL的監控,MySQL預設是關閉的,也就是說MySQL預設不會記錄慢查詢日志,因為為了後續線上問題好排查,項目上線前一定要記得開啟!
  • slow_query_log:設定是否開啟慢查詢日志,預設OFF關閉。
  • slow_query_log_file:指定慢查詢日志的存儲目錄及檔案名。

可以通過這兩個參數來開啟慢查詢日志,如果不設定存儲目錄,預設放在MySQL的具體庫的目錄下。當開啟慢查詢日志的監控後,可以通過設定long_query_time參數,來指定查詢SQL的門檻值:

set global long_query_time = 1;
複制代碼           

其預設機關是秒,是以如果要指定更細粒度的時間,可以通過0.01這種形式設定,0.01表示10ms。當然,該參數也可不設定,不指定門檻值的情況下,預設為10s,即執行時間超過10s的查詢SQL才會記錄到慢查詢日志中。

對于門檻值的設定,并不是随咱們率性而為,這個參數一定要設定合理!因為該參數的大小會直接影響MySQL的性能,比如設定一個0.2s,但如果大量業務SQL執行時都會超出該時長,那最終會導緻MySQL十分頻繁的往慢查詢日志中寫資料。

要記住:慢查詢日志在記憶體中是沒有緩沖區的,也就意味着每次記錄慢查詢SQL,都必須觸發磁盤IO來完成,是以門檻值設的太小,容易使得MySQL性能下降;如果設的太大,又會導緻無法檢測到問題SQL,是以該值一定要設定一個合理值。

問題來了,這個值設成多大合理呢?可以先開啟general log,觀察後實際的業務情況後再決定。

General-log查詢日志

general log即查詢日志,MySQL會向其中寫入所有收到的查詢指令,如select、show等,同時要注意:無論SQL的文法正确還是錯誤、也無論SQL執行成功還是失敗,MySQL都會将其記錄下來。對于該日志可以通過下述參數開啟:

  • general_log:是否開啟查詢日志,預設OFF關閉。
  • general_log_file:指定查詢日志的存儲路徑和檔案名(預設在庫的目錄下,主機名+.log)。

項目測試階段,可以先開啟查詢日志,然後壓測所有業務,緊接着再分析日志中SQL的平均耗時,再根據正常的SQL執行時間,設定一個偏大的慢查詢門檻值即可(這是個笨辦法,如果項目規模較大,直接設定一個大概值,然後上灰階釋出,走正式的營運場景效果會更佳)。

當然,壓測階段結束後,項目正式上線前,一定要記得關閉普通查詢日志!!

六、Relay-log中繼日志

relay log在單庫中是見不到的,該類型的日志僅存在主從架構中的從機上,主從架構中的從機,其資料基本上都是複制主機bin-log日志同步過來的,而從主機複制過來的bin-log資料放在哪兒呢?也就是放在relay-log日志中,中繼日志的作用就跟它的名字一樣,僅僅隻是作為主從同步資料的“中轉站”。

當主機的增量資料被複制到中繼日志後,從機的線程會不斷從relay-log日志中讀取資料并更新自身的資料,relay-log的結構和bin-log一模一樣,同樣存在一個xx-relaybin.index索引檔案,以及多個xx-relaybin.00001、xx-relaybin.00002....資料檔案。

對于這個日志的具體參數、工作過程,放在後續的《MySQL高可用-主從讀寫分離篇》闡述。

redo log與binlog

雖然可能大部分文章都有介紹過,但為了文章的完整性,我們還是從redo log和binlog的差別聊起。

位置不同

首先就是兩個日志所處的位置不同了,mysql的整體架構可分為server層和存儲引擎層,mysql采用插拔式的存儲引擎,常見的存儲引擎有myisam、innodb、memory等,在建立表時指定要使用的存儲引擎(create table .... engine=innodb)。

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

binlog是存在于server層的日志,也就是無論使用哪種存儲引擎,都能使用binlog記錄執行語句。而redolog是innodb存儲引擎特有的。

大小不同

binlog分多個日志檔案記錄,單個檔案的大小通過max_binlog_size設定,采用追加的方式寫入,當binlog 大小超過max_binlog_size設定的大小會建立新的日志檔案,然後切換到新檔案繼續寫入。此外,可通過expire_logs_days設定binlog日志保留的天數。

redolog 的大小是固定的,在 mysql 中可以通過修改配置參數 innodb_log_files_in_group 和 innodb_log_file_size 配置日志檔案數量和每個日志檔案大小,采用循環寫的方式記錄,當寫到結尾時,會回到開頭循環寫日志。

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

記錄内容不同

binlog記錄操作的方法是邏輯性的語句。有statement和row兩種記錄格式, statement格式記 sql 語句;row 格式會記錄更新前和更新後行的内容.

而redolog記錄的是資料庫中每個頁的修改。比如“在某個資料頁上做了什麼修改”

二階段更新流程

了解了兩種日志的差別後,我們再來通過一條更新語句的執行流程來看看這兩個日志分别如何寫入的。語句内容為update t set a = a + 1 where id = 1

  1. 執行器通過innodb引擎擷取id = 1的資料,如果資料本身在記憶體中,會直接傳回給執行器;否則,先從磁盤中讀入記憶體,再傳回。
  2. 執行器拿到引擎給的行資料,把這個值加上 1,得到新的一行資料,再調用引擎接口寫入這行新資料。
  3. 引擎将這行新資料更新到記憶體中。然後将對記憶體資料頁的更新内容記錄在 redolog buffer中,此時,buffer中的這條語句狀态為prepare。然後告知執行器執行完成了,随時可以送出事務。
  4. server層送出事務時,會先将這個操作的日志寫入binlog buffer中,再調用引擎的事務送出接口,引擎會将剛寫入的redolog記錄狀态修改為commit。更新完成。
MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

可以發現,一次更新後,不僅資料存在記憶體中,redolog和binlog也是先寫到記憶體中,之後再根據設定的落盤機制進行日志落盤。

日志落盤

binlog落盤政策

mysql通過sync_binlog參數來控制binlog buffer的日志落盤政策。

sync_binlog = 0, 表示mysql不控制binlog的重新整理,使用檔案系統的緩存重新整理政策,這時性能最好,同時風險也是最大的,一旦系統crash,binlog buffer中的日志資料都将丢失。

sync_binlog = 1 表示每次送出事務都會将buffer中的日志資料同步刷到磁盤中,最安全但由于刷盤頻率較高,性能也是最差的。

sync_binlog > 1 表示binlog buffer每寫入sync_binlog次事務後,再刷日志資料到磁盤中。

redolog落盤政策

在講redolog持久化之前,我們先了解下write和fsync兩個系統調用,作業系統中,記憶體被劃分為使用者空間和核心空間,使用者空間存放着應用程式的緩存資料,redolog buffer就存在于使用者空間中,要把使用者空間的資料持久化到磁盤中,需要先調用write系統調用,把資料先寫入核心空間,之後再調用fsync系統調用,将核心空間的資料寫入到磁盤中。

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

mysql通過innodb_flush_log_at_trx_commit參數控制redo log buffer寫入磁盤的時機。

innodb_flush_log_at_trx_commit = 0 表示事務送出時,日志繼續儲存在redolog buffer中,根據innodb_flush_log_at_timeout設定的間隔調用write和fsync将日志持久化到磁盤中,innodb_flush_log_at_timeout預設為1,也就是日志每秒寫入到磁盤中。批量寫入,io性能較好,但資料丢失風險較大。

innodb_flush_log_at_trx_commit = 1 表示事務送出時,都将調用write和fsync将日志寫入磁盤。這種方式不會丢失任何資料,但io性能較差。

innodb_flush_log_at_trx_commit = 2 表示事務送出時,都會調用write将日志寫入到核心緩存中,之後每秒調用fsync将日志寫入磁盤。這種也比較安全,即使mysql程式奔潰了,os buffer中的日志也不會丢失。當然,如果作業系統也奔潰了,這部分日志也就不見了。

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

Q&A

Q: 處于prepare狀态的redolog會被重新整理到磁盤中嗎?

A: 會的,例如同一時刻,有a和b兩個事務,a處于prepare,b進行commit觸發日志刷盤,這時會把a的redo日志也刷到磁盤中。

Q: binlog是否是多餘的,可以使用redolog代替binlog嗎?

A: 首先,就支援事務方面,binlog确實用處是不大的,在奔潰恢複的時候需要通過binlog确定事務是否該送出也隻是避免binlog被應用到備庫上了,如果主庫直接復原會導緻主備資料不一緻。

但binlog的”歸檔“功能是redolog不具備的。redolog大小固定,采用循環寫,較早的日志會被覆寫,無法持久儲存,而binlog是不限制大小的,日志追加寫入。隻要有保留binlog日志,可以恢複資料庫任何時刻的狀态。

Q: binlog和redolog的幾種落盤政策,也是頻繁寫磁盤,與直接資料寫磁盤有什麼差別嗎?

A: 日志檔案是存儲在連續的若幹個資料頁中的,是以在寫日志到磁盤時隻需要進行一次尋址,屬于順序讀寫;而寫資料時,一次事務可能需要改動的資料可能涉及好幾個離散的資料頁,寫磁盤時需要進行多次「尋道->旋轉」的尋址過程,屬于随機讀寫,速度比順序讀寫差了好幾個數量級。

資料落盤

為了避免頻繁寫入磁盤導緻的性能瓶頸,資料頁先在記憶體中修改,在記憶體中發生過修改的頁稱為髒頁(因為此時頁中的資料與磁盤的不一緻,是”髒“的), 改動的資料頁需要找時間同步到磁盤中,這個過程稱為”刷髒頁”。

LSN

在innodb中,每對一個資料頁的修改,都會生成一個8位元組的序列号lsn來标記版本,lsn的值全局單調遞增,随着日志的寫入而逐漸增大,lsn存在于資料頁和redo log中。

在整個更新過程中,有幾個lsn比較值得關注:

  1. 修改記憶體資料頁中的資料時,會更新記憶體資料頁中的LSN,暫稱為data_in_buffer_lsn。
  2. 向redolog buffer寫入日志時,會記錄下對應的LSN,暫稱為redo_log_in_buffer_lsn。
  3. 當觸發到redolog的幾種刷盤政策時,會将redolog buffer中的日志刷入磁盤中,并在該檔案記下對應的LSN,暫稱為redo_log_on_disk_lsn。
  4. 資料從記憶體中刷到磁盤時,會在磁盤上對應的資料頁記錄下目前的LSN,暫稱為data_on_disk_lsn。
  5. innodb會在适當的時候将redolog上記錄的對應資料頁的改動同步到磁盤中,同步進度也是通過lsn标示,稱為checkpoint_lsn。(後文會詳細介紹)

可以通過show engine innodb status檢視各lsn的值。

lsn可以了解為資料庫從建立以來産生的 redo 日志量,這個值越大,說明資料庫的更新越多,也可以了解為更新的時刻。此外,每個資料頁上也有一個 lsn,表示最後被修改時的 lsn,值越大表示越晚被修改。比如,資料頁 A 的 lsn 為 100,資料頁 B 的 lsn 為 200,checkpoint lsn 為 150,系統 lsn 為 300,表示目前系統已經更新到 300,小于 150 的資料頁已經被刷到磁盤上,是以資料頁 A 的最新資料一定在磁盤上,而資料頁 B 則不一定,有可能還在記憶體中。

下面我們來讨論下innodb中發生刷髒頁的幾種時機。

資料落盤時機

定時重新整理

innodb的主線程會定時将一定比例的髒頁重新整理到磁盤中,這個過程是異步的,不會影響到查詢/更新等其他操作。

系統記憶體不夠用

innodb會維護一個記憶體資料頁的lru清單,并通過一個單獨的page clear線程來保證一定的空閑資料頁,當空閑頁不足時,會将lru尾部的記憶體頁淘汰掉,如果淘汰的頁中有髒頁,會先将髒頁資料重新整理到磁盤中。

髒頁比例過高

innodb中,有個innodb_max_dirty_pages_pct參數,用于控制髒頁在記憶體中的占比,當髒頁比例超過設定的比例後,會重新整理一部分髒頁到磁盤中。

mysql> show variables like 'innodb_max_dirty_pages_pct';
+----------------------------+-----------+
| Variable_name              | Value     |
+----------------------------+-----------+
| innodb_max_dirty_pages_pct | 90.000000 |
+----------------------------+-----------+
           

資料庫正常關閉

參數innodb_fast_shutdown控制着資料庫關閉時的落盤政策,當設定為1時,會将所有的日志髒頁和資料髒頁都重新整理到磁盤中;設定為2時,僅保證日志落盤。

redo log checkpoint刷盤

再回顧下更新的流程,更新操作記錄到redolog,資料更新到記憶體中,整個更新操作就算結束了。 如果資料庫異常關閉了,下次啟動時,我們需要根據redolog将相應的資料頁的資料改動恢複回來。

但redolog大小是固定的,采用循環寫的模式,寫到結尾時,會回到開頭循環寫日志。 是以,随着更新操作次數的積累,redolog上的記錄會被覆寫掉,有些改動也就丢失了。

那不限制redolog的大小可以嗎?

可以試想下,redolog達到1TG,資料庫資料量有10TG,異常重新開機時,為了恢複資料頁的改動。我們需要讀取1T的日志進行恢複。如果全部的資料頁都發生了修改,我們還需要将10TG的資料全部載入到記憶體中。 是以,不限制redolog大小後,會出現另外兩個問題:

  1. 恢複速度較慢;
  2. 記憶體無法緩存資料庫所有的資料。

redolog 采用checkpoint政策,會定期将redolog上的資料修改逐漸重新整理到磁盤中,同步進度用lsn标示,稱為checkpoint_lsn。redolog根據checkpoint_lsn可以劃分為兩部分,小于checkpoint_lsn的日志對應的資料頁改動已經重新整理到磁盤中,這部分日志可被覆寫重新寫入;大于checkpoint_lsn部分日志對應改動還未同步到磁盤。

MySQL日志篇之undo-log、redo-log、bin-log傻傻分不清!

redolog checkpoint刷盤分為異步刷盤和同步刷盤。

checkpoint_age = redo_lsn - checkpoint_lsn
async_water_mark = 75% * total_redo_log_file_size
sync_water_mark = 90% * total_redo_log_file_size
           

checkpoint_age < async_water_mark, 表示目前髒頁資料較少,不會觸發redolog checkpoint刷盤。

async_water_mark < checkpoint_age < sync_water_mark, 會異步将一定量的髒頁重新整理到磁盤中,使得滿足checkpoint_age < async_water_mark。異步重新整理不會影響其他更新操作。

checkpoint_age > sync_water_mark, 當redolog容量設定的較小,同時進行大量的更新操作,導緻剩餘可使用的日志較少,會觸發同步重新整理,将髒頁重新整理到磁盤中,直到滿足checkpoint_age < async_water_mark,同步重新整理會阻塞使用者的更新操作。

Q&A

Q: 除了redolog checkpoint,其他幾種情況刷髒頁會推動checkpoint_lsn嗎?

A: 不會。緩沖池會維護一個管理髒頁的flush_list, 一個資料頁因修改了資料成為髒頁後,會添加到flush_list中,髒頁在重新整理到磁盤中後,會從flush_list中去掉。

flush_list按資料頁的最早修改 lsn (oldest_modifcation) 從小到大排序。比如一個幹淨頁變為髒頁後,data_in_buffer_lsn=100,在flush_list的位置為1,當資料頁再次發生改動時,data_in_buffer_lsn變為120,但在flush_list的位置不變。

進行redo checkpoint時,選擇的日志隻需要與flush_list上最老的頁(擁有flsuh_list上最小的lsn)進行比較即可:

page_noflush_list != page_noredo,表示該髒頁資料已被同步到磁盤中,推進checkpoint_lsn。 page_noflush_list == page_noredo,将該髒頁重新整理到磁盤中,推進checkpoint_lsn。

Q: checkpoint資訊存在哪?如何存儲?

A: checkpoint資訊存儲在第一個redo日志檔案的檔案頭中。儲存采用雙份存儲,輪流讀寫的方式。

在第一個redo日志檔案的檔案頭中有兩個地方用于存儲checkpoint資訊,記錄時來回讀取這兩個checkpoint域。假設隻有一個 checkpoint 域,當更新checkpoint一半時,伺服器也挂了,會導緻整個 checkpoint 域不可用。這樣資料庫将無法做崩潰恢複,進而無法啟動。如果有兩個 checkpoint 域,那麼即使一個寫壞了,還可以用另外一個嘗試恢複,雖然有可能這個時候日志已經被覆寫,但是至少提高了恢複成功的機率。兩個 checkpoint 域輪流寫,也能減少磁盤扇區故障帶來的影響。

奔潰恢複

使用者修改資料并成功送出了事務,此時資料改動在記憶體中還未落盤,如果這個時候資料庫挂了,重新開機後,需要從日志中将成功送出的事務資料改動恢複後重新寫入磁盤,保證資料不丢失,同時還要復原沒有送出的事務。奔潰恢複中,除了需要redolog和binlog日志,還離不開undo日志的支援。

undo log

進行更新操作時,都會産生undo日志:當 delete 一條記錄時,會記錄一條對應的 insert 日志。當 update 一條記錄時,會記錄一條對應相反的 update 日志.當insert一條記錄時,會記錄一條delete日志。

需要復原事務時,隻需要執行對應的undo操作,就可以将資料恢複。此外,通過undo日志,可以保證事務的隔離性。假設隔離級别設為讀送出,當未送出的事務A修改了id=1對應的行資料,此時事務B想要讀取id=1的資料,可以先拿着最新版本的資料,順着undo日志找到滿足其可見性的記錄。

undo日志與普通的資料頁一樣,對于undo頁的修改,需要先寫redo日志。也可能會由于lru的規則被淘汰出記憶體,之後再從磁盤中讀取。

奔潰恢複流程

整個奔潰恢複流程可以分為redo前滾和undo復原兩部分。

redo前滾

對于checkpoint_lsn之前的日志,對應改動已經落盤,不需要關心。首先初始化一個hash_table,掃描checkpoint_lsn之後的日志,将同一個資料頁的日志分發到hash_table的相同位置,并按日志的lsn從小到大排序。掃描後,周遊整個哈希表,依次應用每個資料頁的日志。應用完後,記憶體中資料頁的狀态就恢複到了奔潰之前。

undo復原

接着,初始化undo日志,按操作類型分為undo_insert_list 和 undo_update_list,周遊兩個連結清單,根據日志中記錄的事務的狀态重建事務狀态,TRX_ACTIVE表示需要復原,TRX_STATE_PREPARED表示可能需要復原。然後将事務加入到trx_list連結清單中,之後,周遊trx_list,按照事務的不同狀态復原或送出。對于TRX_ACTIVE狀态的事務,利用undo日志直接復原;對于TRX_STATE_PREPARED狀态的事務,根據server層的binlog來決定是否復原,如果binlog已經寫了并且日志是完整的,則送出該事務,否則就復原。

Q&A

Q: undo日志什麼時候會删除?

A: undo按操作類型可分為update/delete/insert, insert操作在事務送出前隻對目前事務可見,産生的 Undo 日志可以在事務送出後直接删除。而update/delete操作,其他事務可能需要老版本資料,需要保留到undo操作對應的事務id比資料庫目前所有的事務快照都小(此時資料庫所有事務對此次改動均可見),才可以删除。

七、日志篇總結

叨叨絮絮下來,就大緻将MySQL中的一些常見、較為重要的日志講明白啦,其實重點搞清楚undo-log、redo-log、bin-log即可,其他的會在後續篇章中再次提到,最後稍微總結一下這三個比較核心的日志:

  • undo-log:主要用于實作事務ACID原則中的原子性和MVCC機制。
  • redo-log:主要用于實作事務原則中的持久性,確定事務送出後就不會丢失。
  • bin-log:主要結合redo-log實作事務原則中的一緻性,確定事務送出前後,資料的一緻。

對于其他幾類日志,在本篇中也僅講明了大概,畢竟後面的章節中會再出現,而對于上述三大日志也就基本上不會提到了,是以剖析的較為全面,那麼咱們下篇見~,下面準備講:《MySQL記憶體篇》,其實也主要是講InnoDB Buffer Pool緩沖區,至于為什麼半道出家的InnoDB能替換掉官方的MyIASM引擎,最大原因也在于此。

繼續閱讀