天天看點

Mysql專欄 - redo log日志細節

前言

本節講述的是redolog日志,介紹redo log寫磁盤的過程以及redo log的随機寫和順序寫,最後我們講介紹關于mysql最常見的事務問題,并且介紹mysql的事務隔離級别以及隔離級别的特性。

概述

redo log 日志在介紹整個innodb的存儲引擎結構的時候進行過介紹,本節将會繼續深入redo log了解内部的結構了解内部的基礎存儲結構,同時了解關于redo log寫入到磁盤的時機,并且了解redo log的日志有幾個,

為什麼要引入redo log?

說白了是為了保證mysql當機的時候,資料恢複可以按照順序的恢複方式而不是随機讀寫,redo log也是保證事務一緻性的關鍵元件。

Redo log寫磁盤的過程

Redo log存放的内容

存放的内容如下:「表空間号+資料頁号+偏移量+修改幾個位元組的值+具體的值」,為了使表示資料頁的内容修改了幾個位元組的值, redo log使用了類型劃分:

  1. Mlog_1byte:表示修改了1個位元組
  2. Mlog_2byte:表示兩個
  3. 以此類推的4位元組和8位元組
  4. 如果修改了很多:「mlog_write_string」

是以一條redo log的格式如下:

日志類型(就是類似MLOG_1BYTE之類的),表空間ID,資料頁号,資料頁中的偏移量,具體修改的資料

下面是對應的資料結構圖:

Mysql專欄 - redo log日志細節

Redo log block 存放資料頁

Redo log**「不是」按照單行寫入日志檔案的,而是使用block來進行管理,一個redo log block 為512位元組。内部包含了12位元組的header塊,496位元組的body塊,和4位元組的trailer 塊尾,而12個位元組的資料頭又包含了:「4個位元組的塊編号,2個位元組寫入的資料長度,2個位元組第一個日志的分組偏移量,4個位元組的check point no」**,最終他的資料結構如下

Mysql專欄 - redo log日志細節

redolog行如何存儲

在寫入資料的時候,redo log一行日志是如下的形式進行寫入的,由于redo log并不是單行存儲,而是使用連續的512位元組的block塊進行存放的,當然這個操作是在記憶體中的redolog buffer中完成,當緩沖池中一個塊被寫滿之後,是肯定需要刷入到磁盤的,是以此時會有一個背景線程不斷的把資料寫入到redo log的磁盤檔案中,最終他的效果就如同下面這般:

上面我們介紹的redo log一行的存儲結構,在這裡就可以派上用場,redo log的行資料不斷寫入到body到資料塊裡面,并且背景線程不斷的把資料重新整理到磁盤當中,當一行資料的不斷寫入,到達一個block塊的大小的時候,就需要把它寫入到磁盤的檔案裡面,寫入完成之後,記憶體完成操作,将會把資料寫入到具體的磁盤對應的塊裡面也就是磁盤檔案的内部。

Mysql專欄 - redo log日志細節

redo log buffer存儲redo log block

Redo log的資料和緩沖池一樣也是使用緩存的方式刷入到磁盤的,redo log buffer會在mysql啟動的時候申請一塊連續的記憶體空間來存放 redo log block,他的格式如下所示:

Mysql專欄 - redo log日志細節

我們可以設定參數:「innodb_log_buffer_size」 來指定redo log buffer的大小,預設值為16mb,這個大小足夠大了,畢竟一個block為512位元組,在執行大批量資料操作的時候,可能會出現多個redo log 為一組的情況,也就是資料跨越了多個redo log頁進行存儲。

Redo log 日志什麼時候寫入磁盤

關于這個内容我們需要了解下面兩個問題:

  1. Redo log日志檔案有幾個?
  2. Redo log block什麼時候會把資料刷入到磁盤?

重新整理到磁盤的時機

mysql觸發下面的條件的時候會把redo log buffer 重新整理到磁盤當中:

  1. 超過redo log buffer 的一半大小
  2. Redo log需要在事務送出的時候,需要把redo log對應的redo log block重新整理到磁盤中,同時為了保證事務正确送出,redo log存在重做日志。
  3. 背景線程1秒一次重新整理資料到磁盤
  4. mysql關閉會把redo log 全部重新整理到磁盤

這裡需要記住一個重點:「隻有在redo log 的内容重新整理到磁盤,事務送出才算是成功,隻有這樣才能算是事務的正确」。

​ 實際上預設情況下redo log都會寫入一個目錄中的檔案裡,這個目錄可以通過​

​show variables like 'datadir'​

​來檢視,可以通過**「innodb_log_group_home_dir」**參數來設定這個 目錄的。

Redo log日志檔案有幾個

redo log是有多個的,寫滿了一個就會寫下一個redo log,而且可以限制redo log檔案的數量,通過**「innodb_log_file_size」可以指定每個redo log檔案的大小,預設是「48MB」**,通過 **「innodb_log_files_in_group」**可以指定日志檔案的數量,預設就2個,也就是96MB,可能看上去很小,但是實際上這個檔案數量存個百萬千萬的資料完全不是問題。

是以在上面提到的目錄中裡就兩個日志檔案,分别為**「ib_logfile0」和「ib_logfile1」**,每個48MB,最多就這2個日志檔案,就是先寫第一個,寫滿了寫第二個,那第二個寫滿了怎麼辦?繼續回去寫第一個即可。

如果不想使用預設的大小如何處理呢,其實調節上述兩個參數就可以了,比如每個redo log檔案是96MB, 最多保留100個redo log檔案等等。

Undo log配合 redo log進行日志復原

Undo log需要配合redo log進行下面的記錄操作:

  • 删除需要把删除之前的資料插入回去
  • 更新需要舊值更新回去
  • 新增需要把新增之後的資料删除掉

最終undo log日志的結構如下:

Mysql專欄 - redo log日志細節

Redo log并發問題

多個事務并發執行的時候,可能會同時對緩存頁裡的一行資料進行更新,這個沖突怎麼處理?是否要加鎖?可能有的事務在對一行資料做更新,有的事務在查詢這行資料,這裡的沖突怎麼處理?

髒寫、髒讀、不可重複讀、幻讀(重點)

1. 髒寫

根據下面的圖我們說下髒寫,當事務A更新一行事務B準備更新的資料,在事務A更新之後事務B執行了更新操作,然後A更新完成之後卻復原了,并且在B更新之後把值改了回去。這樣的操作結果就是:「B操作完結果卻是NULL」,本質就是事務B更新了一個事務A操作過的資料,結果事務A沒有送出,但是一旦更新會随時影響事務B的操作結果。

Mysql專欄 - redo log日志細節

2. 髒讀

用最簡單的話其實就是一個事務讀到了一個未送出的值,而一旦這個值失效就會出現業務的問題。

其實一句話總結,無論是髒寫還是髒讀,都是因為一個事務去更新或者查詢了另外一個還沒送出的事務 更新過的資料。因為另外一個事務還沒送出,是以他随時可能會反悔會復原,那麼必然導緻你更新的資料就沒了,或者你之前查詢到的資料就沒了,這就是髒寫和髒讀兩種坑爹場景。

3. 不可重複讀:

不可重複讀:在第一次進行查詢的時候沒有任何幹擾

Mysql專欄 - redo log日志細節

事務B進行更新+送出事務,同時修改一個值,然而A再去讀和上一次不一樣了

Mysql專欄 - redo log日志細節

然後C也插進來,又把這個值給更新了,然後A又去讀了一次,又發現不一樣了

Mysql專欄 - redo log日志細節

不可重複讀并不是嚴重的問題,但是取決于資料庫如何設計,因為重複讀取一個值發生改變或者不改變要根據資料庫的設計者來決定。

這裡有兩種方案:

  • A事務讀取之後,除非他送出事務,否則BC不能做任何更改,或者A讀取一次之後就不會再讀取第二次,或者多次讀取的值都是相同的,也稱為不可重複讀
  • 可重複讀就是上面圖表的情況。

4. 幻讀

幻讀指的就是你一個事務用一樣的SQL多次查詢,結果每次查詢都會發現查到了一些之前沒看到過的資料,幻讀就是讀到了别人沒有送出過的資料。

「不可重複讀和幻讀到底有什麼差別呢?」

(1) 不可重複讀是讀取了其他事務更改的資料,「針對update操作」

解決:使用行級鎖,鎖定該行,事務A多次讀取操作完成後才釋放該鎖,這個時候才允許其他事務更改剛才的資料。

(2) 幻讀是讀取了其他事務新增的資料,「針對insert和delete操作」

解決:使用表級鎖,鎖定整張表,事務A多次讀取資料總量之後才釋放該鎖,這個時候才允許其他事務新增資料。

這時候再了解事務隔離級别就簡單多了呢。

mysql的四種隔離級别:

  • read uncommitted(讀未送出)
  • read committed(讀已送出)
  • repeatable read(可重複讀)
  • serializable(串行化)

隔離級别的特性:

  1. 讀未送出:不允許髒寫發生。其實就是說可以防止兩個線程同時更新一個值。但是不防其他任何内容
  2. 讀已送出:不允許髒寫和髒讀,也是即可防止兩個線程同時更新一個值,又可以防止兩個值同時讀同一個值,也就是一個值肯定讀不到另一個未送出的事務改動的資料情況。但是可能會多次讀到一個值被送出到事務不斷改動的情況
  3. 不可重複讀:也就是可以防止髒寫、髒讀、不可重複讀這三個層級在Mysql中的展現就是,同一個事務無論讀多少次都不會讀到已經送出的業務。但是會出現讀取到其他事務新增或者删除的資料
  4. 串行化:完全同步,每次操作都隻允許一個線程通路

總結

寫在最後