天天看點

MySQL日志:binlog、事務日志(redo、undo)redo logundo logundo+redo事務的簡化過程binlog參考

事務的隔離性是通過鎖實作,而事務的原子性、一緻性和持久性則是通過日志實作。Mysql的日志可以分為:

  • binlog:server層實作
  • 事務日志:包括redo log、undo log,引擎層(innodb)實作

redo log

redo log是用于備份事務中操作得到的最新資料的地方,記錄資料頁的實體修改。

redo log通常是實體日志,記錄的是資料頁的實體修改,而不是記錄某一行或某幾行的修改,它用來恢複送出後的實體資料頁(恢複資料頁,且隻能恢複到最後一次送出的位置)。

在innoDB的存儲引擎中,事務日志通過重做日志(redo log)和innoDB存儲引擎的日志緩沖(redo Log Buffer)實作。事務開啟時,事務中的操作,都會先寫入存儲引擎的日志緩沖中,在事務送出之前,這些緩沖的日志都需要提前重新整理到磁盤上持久化,這就是DBA們口中常說的“日志先行”(Write-Ahead Logging)。當事務送出之後,在Buffer Pool中映射的資料檔案才會慢慢重新整理到磁盤。此時如果資料庫崩潰或者當機,那麼當系統重新開機進行恢複時,就可以根據redo log中記錄的日志,把資料庫恢複到崩潰前的一個狀态。未完成的事務,可以繼續送出,也可以選擇復原,這基于恢複的政策而定。

即事務在修改資料時,innodb隻修改資料的記憶體拷貝,然後把修改行為記錄到持久在磁盤的事務日志(redo log)中,在事務日志持久化後,記憶體中被修改的資料在背景慢慢刷回磁盤。

即修改資料時需要寫兩次磁盤:

  • 先将操作記錄到redo log buffer,然後持久化redo log到磁盤
  • 将redo log中記錄的資料持久化到磁盤

為什麼需要redo log(為什麼采用兩次寫磁盤,而不直接持久化新資料?)

既然redo log是将資料持久化到磁盤上,那麼必然也對應一次IO操作,那麼每次修改資料直接将其重新整理到磁盤的話,也是一次IO操作,按道理說效率應該一樣,那麼為什麼還要引入redo log?

事實上,redo log寫入檔案尾是順序寫過程,雖然也是一次IO操作但磁頭不需要移動(即事務日志采用追加的方式,是以是順序IO)。而每次都将資料更新到磁盤其實是将 buffer pool中的資料緩存頁寫入磁盤的資料也中,其中涉及到要尋找資料頁在哪的操作,這是一個随機寫過程,需要移動磁頭,磁盤的随機寫過程更耗時,耗資源,是以引入redo log是很有必要的。

undo log

undo log是用于事務中,在操作資料之前,備份需操作的資料的地方,undo log 記錄資料的邏輯修改。

undo log用來實作事務的原子性,在innodb引擎中還用來實作事務的多版本并發控制。

undo log記錄了資料在每個操作前的狀态,如果事務執行過程中需要復原,就可以根據undo log進行復原操作。單個事務的復原,隻會復原目前事務做的操作,并不會影響到其他的事務做的操作。

即在操作資料庫之前先将資料備份到一個地方,這個存儲資料備份的地方就是undo log。然後再進行資料的修改,如果中途發生錯誤或者使用者執行的rollback語句,系統就可以利用undo log中的備份将資料回複到事務開始前的狀态。

undo log 是邏輯日志,可以了解為每次操作之前都會記錄相反反操作:

  • 當delect一條語句時,undo log 中會記錄一條對應的insert語句
  • 當insert一條語句時,undo log 中會記錄一條對應的delect語句
  • 當update一條語句時,undo log 中會記錄一條相反的update語句

undo+redo事務的簡化過程

假設有2個數值,分别為A和B,值為1,2

1. start transaction;

    2. 記錄 A=1 到undo log;
    3. update A = 3;
    4. 記錄 A=3 到redo log buffer;
    5. 記錄 B=2 到undo log;
    6. update B = 4;
    7. 記錄B = 4 到redo log buffer;
    8. 将redo log buffer 重新整理到磁盤
    9. 執行器(Server層)生成寫操作的binlog日志,并将binlog寫入磁盤(通過XA事務保證redo log 和 binlog的一緻性)
    10. commit
           

在1-8的任意一步系統當機,事務未送出,該事務就不會對磁盤上的資料做任何影響。如果在8-10之間當機,恢複之後可以選擇復原,也可以選擇繼續完成事務送出,因為此時redo log已經持久化。若在10之後系統當機,記憶體映射中變更的資料還來不及刷回磁盤,那麼系統恢複之後,可以根據redo log把資料刷回磁盤。

是以,redo log其實保障的是事務的持久性和一緻性,而undo log則保障了事務的原子性。

binlog

binlog是server層的日志,記錄所有資料庫表結構變更(例如CREATE、ALTER TABLE…)以及表資料修改(INSERT、UPDATE、DELETE…)的二進制日志。

當執行寫操作的SQL(INSERT、UPDATE、DELETE…)時,會把相應的SQL寫入到對應的binlog,可以簡單了解為binlog是記錄資料庫增删改,不記錄查詢的二進制日志,可以利用binlog進行備份、資料恢複。

binlog的三種模式

(1)Row Level 行模式

日志中會記錄每一行資料被修改的形式,然後在slave端再對相同的資料進行修改

  • 優點:在row level模式下,bin-log中可以不記錄執行的sql語句的上下文相關的資訊,僅僅隻需要記錄那一條被修改。是以rowlevel的日志内容會非常清楚的記錄下每一行資料修改的細節。不會出現某些特定的情況下的存儲過程或function,以及trigger的調用和觸發無法被正确複制的問題
  • 缺點:row level,所有的執行的語句當記錄到日志中的時候,都将以每行記錄的修改來記錄,會産生大量的日志内容。

(2)Statement Level(預設)

每一條會修改資料的sql都會記錄到master的bin-log中。slave在複制的時候sql程序會解析成和原來master端執行過的相同的sql來再次執行

  • 優點:statement level下的優點首先就是解決了row level下的缺點,不需要記錄每一行資料的變化,減少bin-log日志量,節約IO,提高性能,因為它隻需要在Master上鎖執行的語句的細節,以及執行語句的上下文的資訊。
  • 缺點:由于隻記錄語句,是以,在statement level下 已經發現了有不少情況會造成Mysql的複制出現問題,主要是修改資料的時候使用了某些定的函數或者功能的時候會出現。

(3) Mixed 自動模式

在Mixed模式下,MySQL會根據執行的每一條具體的sql語句來區分對待記錄的日志格式,也就是在Statement和Row之間選擇一種。如果sql語句确實就是update或者delete等修改資料的語句,那麼還是會記錄所有行的變更。

binlog與redolog 的差別

  • binlog主要用于備份、主從同步;redo log用于實作事務的一緻性和持久性,防止資料丢失。
  • redo log 是innodb引擎特有的,而binlog由服務層實作是所有存儲引擎都可以使用的
  • redo log是實體日志,記錄的是某頁上具體的做的修改。binlog是邏輯日志,記錄的是這個語句的原始邏輯。
  • redo log是循環寫過程,空間有限,空間不夠時會覆寫之前的資訊。binlog是追加寫過程不會覆寫之前的資訊。

執行器(Server)和InnoDB引擎在執行update内部流程

1、執行器先找到ID=2這一行,ID是主鍵,引擎用樹搜尋找到這一行,如果ID=2這一行所在的資料頁本來就在記憶體中,就直接傳回給執行器。否則需要從磁盤中讀入磁盤中,然後再傳回。

2、執行器拿到引擎給的行資料,把這個值加上1,得到一行新的資料,再調用引擎接口,寫入這行資料。

3、引擎将這行新的資料寫入到記憶體中,同時将這個更新操作記錄到redo log裡面,此時redo log處于prepare狀态,然後告知執行器執行完成了,随時可以送出事務。

4、執行器生成這個操作的binlog日志,并将binlog寫入磁盤。

5、執行器調用引擎的送出事務的接口,引擎把剛剛寫入的redo log改成送出(commit)狀态,更新完成。

MySQL日志:binlog、事務日志(redo、undo)redo logundo logundo+redo事務的簡化過程binlog參考

binlog 和 redo log一緻性

此部分為引用,詳細請閱讀:MySQL中binlog和redo log的一緻性問題

在MySQL内部,在事務送出時利用兩階段送出(内部XA的兩階段送出)很好地解決了binlog和redo log的一緻性問題:

  • 第一階段: InnoDB Prepare階段。此時SQL已經成功執行,并生成事務ID(xid)資訊及redo和undo的記憶體日志。此階段InnoDB會寫事務的redo log,但要注意的是,此時redo log隻是記錄了事務的所有記錄檔,并沒有記錄送出(commit)日志,是以事務此時的狀态為Prepare。此階段對binlog不會有任何操作。
  • 第二階段:commit 階段,這個階段又分成兩個步驟。第一步寫binlog(先調用write()将binlog記憶體日志資料寫入檔案系統緩存,再調用fsync()将binlog檔案系統緩存日志資料永久寫入磁盤);第二步完成事務的送出(commit),此時在redo log中記錄此事務的送出日志(增加commit 标簽)。

可以看出,此過程中是先寫redo log再寫binlog的。但需要注意的是,在第一階段并沒有記錄完整的redo log(不包含事務的commit标簽),而是在第二階段記錄完binlog後再寫入redo log的commit 标簽。還要注意的是,在這個過程中是以第二階段中binlog的寫入與否作為事務是否成功送出的标志。

通過上述MySQL内部XA的兩階段送出就可以解決binlog和redo log的一緻性問題。資料庫在上述任何階段crash,主從庫都不會産生不一緻的錯誤。

參考

  • 推薦閱讀:MYSQL中的事務日志
  • 推薦閱讀:日志系統:redo log和binlog
  • 好文:MySQL中的事務日志
  • 詳細分析MySQL事務日志(redo log和undo log)
  • MySQL binlog三種模式
  • 深入了解MySQL之redo日志

繼續閱讀