mysql redo log研究

redo log基礎

重做日誌用來實現事務的原子性和持久性,即事務ACID中的A和D。其由兩部分組成:

一是內存中的重做日誌緩衝(redo log buffer),其是易失的:

二是重做日誌文件(redo log file),其是持久的。

redo log buffer

–innodb_log_buffer_size:通常8M已經足夠使用了(上圖是16M),重做日誌緩衝一般不需要設置得很大,因爲一般情況下每一秒鐘會將重做日誌緩衝刷新到日誌文件,因此用戶只需要保證每秒產生的事務量在這個緩衝大小之內即可。

由log block組成,每個log block 512字節(不需要double write)

爲什麼不需要double write呢?

由於重做日誌塊的大小和磁盤扇區大小一樣,都是512字節,因此重做日誌的寫入可以保證原子性,不需要double write

redo log file

–innodb_log_file_size:線上應該設置得大一些 表示物理上的位於innodb存儲引擎數據目錄下的ib_logfile0,ib_logfile1大小?

–innodb_log_files_in_group:可以看到file0,file1兩個文件

重做日誌實現Durable的過程:

InnoDB是事務的存儲引擎,其通過Force Log at Commit機制實現事務的持久性,即當事務提交(COMMIT)時,必須先將該事務的所有日誌寫入到重做日誌文件進行持久化,待事務的COMMIT操作完成纔算完成。

  • 問題1:ib_logfile0和ib_logfile1是循環覆蓋的,並不做歸檔。爲什麼不需要歸檔呢?

    原因是mysql中有二進制日誌

  • 問題2:爲什麼要把重做日誌設得很大呢?

    因爲redo log是做覆蓋的,一個一個512字節的block來寫,如果我將覆蓋寫一個block,但是這個block中對應的髒頁還沒有刷盤,那麼寫redo log就需要進行等待了(強制進行一次髒頁的刷盤),而導致mysql數據庫性能有很大程度的下降。

redo log buffer刷新條件

  1. master thread每秒進行刷新

  2. redo log buffer使用大於1/2進行刷新

  3. 事務提交時進行刷新

    innodb_flush_log_at_timeout 參數,可以設置刷新間隔,默認爲1

    innodb_flush_log_at_trx_commit={0|1|2}

    0 - 事務提交的時候並不把日誌(redo log buffer)寫入到磁盤(1s或者大於1/2時刷日誌)

    1 - 事務每次提交的時候要確保日誌(redo log buffer)寫入磁盤,即使宕機,也可以通過redo恢復,達到持久性的要求

    2 - 事務提交的時候,僅將日誌(redo log buffer)寫入到操作系統緩存

0的話可能會有事務數據丟失。最壞的情況是丟失1秒的事務數據

1的事務數據是不會丟失的,爲什麼呢?

詳細分析一下這個過程

上圖中存在兩個問題

fsync到本地磁盤文件ib_logfile0或ib_logfile1中

通過double write刷盤到磁盤中表的.ibd文件中

2的話,如果是mysql實例掛了,那麼在mysql重啓後,由於數據仍在緩存中,還是會繼續寫入redo日誌的,不會丟失事務數據;但如果是服務器down了,那麼這部分事務日誌還是丟失了

redo log裏面記錄的到底是什麼呢?

redo日誌的分類

物理日誌:記錄整個頁的變化(diff)

邏輯日誌:Like SQL語句

物理邏輯日誌:根據頁進行記錄,記錄的是內容邏輯

space + page_no 指定頁

redo log body:記錄邏輯日誌,並不是記錄sql語句,記錄的是頁的變化

格式如下面兩個insert/delete格式

redo log與binlog對比

在事務提交的時候,並不僅僅寫redo log,還會記錄binlog。爲什麼需要記錄兩份呢?

原因與mysql的基礎架構有關。mysql支持多種存儲引擎,那麼redo log只是innodb引擎使用的,那麼如何保證跨引擎的情況呢?在mysql的上層還需要記錄到binlog中

二進制日誌(binlog),其用來進行point in time(PIT 基於時間點的恢復)的恢復及主從複製(Replication)環境的建立。

從表面上看其和重做日誌非常相似,都是記錄了對於數據庫操作的日誌。然而,從本質上來看,兩者有着非常大的不同。

  1. 重做日誌是在InnoDB存儲引擎層產生,而二進制日誌是在MySQL數據庫的上層產生的,並且二進制日誌不僅僅針對於InnoDB存儲引擎,MySQL數據庫中的任何存儲引擎對於數據庫的更改都會產生二進制日誌。

  2. 兩種日誌記錄的內容形式不同。MySQL數據庫上層的二進制日誌是一種邏輯日誌,其記錄的是對應的SQL語句。而InnoDB存儲引擎層面的重做日誌是物理格式日誌,其記錄的是對於每個頁的修改。

  3. 兩種日誌記錄寫入磁盤的時間點不同,二進制日誌只在事務提交完成後進行一次寫入。而InnoDB存儲引擎的重做日誌在事務進行中不斷地被寫入,這表現爲日誌並不是隨事務提交的順序進行寫入的。

    *代表提交

    二進制日誌僅在事務提交時記錄,並且對於每一個事務,僅包含對應事務的一個日誌。而對於lnnoDB存儲引擎的重做日誌,由於其記錄的是物理操作日誌,因此每個事務對應多個日誌條目,並且事務的重做日誌寫入是併發的,並非在事務提交時寫入,故其在文件中記錄的順序並非是事務開始的順序。

    比如說我一個事務是更新一張幾百萬條記錄的表,那麼產生的binlog日誌估計有幾百M,那麼我commit的時候就是commit這幾百M的日誌。這時候commit就會發生類似等待的效果(其在寫binlog的日誌)

    假設redo log也是200M,但由於其每秒鐘就會fsync一次,其在不停的寫,所以並不會發生類似等待的效果

  4. redo log是寫在redo log buffer中的

    而binlog是寫在binlog cache中的,32K大小

    比如說我一個大事務產生200M的日誌,那麼怎麼辦呢?裝不下那就直接寫到磁盤中去。

    可以定期看一下binlog_cache_disk_use這個值是否比較大,如果比較大,那說明binlog cache可能不夠大

    mysql用在OLTP下,一般是併發量大+小事務的情況,那麼binlog寫入這個問題應該並沒有什麼關係

    在mysql中有一個優化的點:就是把一個大事務拆成小事務去執行,原因1:這樣執行起來比較快(binlog寫的比較快)。原因2:主從複製的時候,延時會小

    oracle中就沒有binlog,只用redo log,複製是基於redo log來做

保證redo log與bin log日誌一致性

我們事務提交的時候,要寫redo log和binlog,

  • 爲什麼要保證兩者的一致性呢?

    其實是爲了保證主從複製的一致性

  • 如何保證這兩個日誌的一致性呢?

    在這裏面使用了mysql內部分佈式事務,類似TCC

當事務commit之後,會執行以下操作

  1. innodb redo log 的 prepare log

  2. write binary log file

  3. innodb redo log 的 commit log

    這裏的寫指的都是寫入到磁盤上

    第一步先寫innodb redo log的prepare的日誌,第二步寫mysql的binlog的日誌,第三步寫innodb redo log的commit日誌

    prepare只是寫一個xid

  • 情況1:如果只有第一步成功了,第二步失敗了,

    那麼整個事務就會rollback

  • 情況2:當事務提交的時候,如果我prepare日誌寫入成功了,二進制日誌寫入成功了,但是第三步在寫commit日誌的時候,服務器down機了。

    那麼我起來恢復的時候,我可以檢測到prepare log中記錄了一個xid,binlog中也有這個xid,這兩個log中都有這個xid,就說明這個事務一定要提交,不管第三步有沒有這個commit log了。也就是說這個事務全部都執行完了,就差最後的commit語句了,那麼再恢復的時候,只要執行commit就可以了。

    沒開binlog的話,那麼1,2都不寫,只寫3,恢復的時候,如果redo log沒有commit log,那麼就回滾

組提交 group commit

通過下面的複製圖可以看到 第一步 和 第二步 都是group commit的

組提交中某個失敗,那麼只是rollback這一個事務

大家一定要注意:

無論是寫redo log,還是寫binlog,都是先寫緩存,再fsync到磁盤

innodb_flush_method=O_DIRECT 打開O_DIRECT只保證數據寫入到磁盤,元數據並沒有更新,元數據指定是描述文件的信息,如文件的大小,文件的更新時間。所以無論用不用O_DIRECT,都要使用fsync

一次fsync刷新多個事務

性能提高10~100+倍

先fwrite到OS Cache

再fsync,確保數據刷新到磁盤

對於重做日誌,fsync的作用是什麼?

確保內存中的log buffer中的重做日誌刷新到磁盤文件中ib_logdata0, ib_logdate1

磁盤IOPS決定TPS,TPS決定fsync的數量

那如何在IOPS比較少的情況下增加TPS呢?

數據庫每秒鐘只允許100條數據的update,這其實是很慢的。

如果我的fsync每次只更新一個事務,那就很慢了。

如果每次fsync 事務group,那麼其TPS就增加了

組提交默認是開啓的

使用checkpoint檢查點技術解決redo log不可用問題

什麼情況下重做日誌不可用呢?

重做日誌出現不可用的情況是因爲當前事務數據庫系統對重做日誌的設計都是循環使用的,並不是讓其無限增大的,這從成本及管理上都是比較困難的。重做日誌可以被重用的部分是指這些重做日誌已經不再需要,即當數據庫發生宕機時,數據庫恢復操作不需要這部分的重做日誌,因此這部分就可以被覆蓋重用。若此時重做日誌還需要使用,那麼必須強制產生Checkpoint,將緩衝池中的頁至少刷新到當前重做日誌的位置。

髒頁是如何產生的

  1. 數據頁首先被讀入緩衝池中,當數據頁中的某幾條記錄被更新或者插入新的記錄時,所有的操作都是在Buffer Pool先完成的;
  2. Buffer Pool中的某個頁和磁盤中的某個頁在(Space, Page_Number)上是相同的,但是其內容可能是不同的(Buffer Pool中的被更新過了),形成了髒頁;
  3. 要定期將緩衝池中的髒頁刷回磁盤(Flush),達到最終一致,即通過CheckPoint機制來刷髒頁;

問題:在buffer pool已經修改了,還沒有刷回去到磁盤之前,數據庫發生down機了,怎麼辦呢?

down機了,也沒關係,因爲每個頁在修改的時候,都將日誌寫入了redo log。

那麼mysql重啓了,通過回放redo log,還是可以實現磁盤數據更新了的

如果按照這個思路,那我完全不需要checkpoint啊,你永遠不刷新髒頁都可以,因爲我有redo log,但問題就是如果redo log很大很大,那恢復起來也非常耗時,所以其實checkpoint是一種縮短數據庫恢復時間的技術

checkpoint檢查點技術就是當緩衝池不夠用時,將髒頁刷新到磁盤,checkpoint技術的基礎是LSN

checkpoint 所做的事情無外乎是將緩衝池中的髒頁刷回到磁盤。不同之處在於刷新多少頁到磁盤,每次從哪裏取髒頁,以及什麼時間觸發Checkpoint

redo log 基於LSN進行恢復

LSN(log sequence number)是什麼呢?

比如一個新的頁,LSN是有一個初始值的,比如說16,這時我對這個頁進行了修改,那會產生redo log日誌,比如說這個日誌爲10個字節,LSN就變爲26,這個時候又進行了修改,產生redo log 20個字節,LSN就變爲46了,所以LSN是一個單調遞增的值,這個是重做日誌中的LSN

在每個頁中也有LSN,表示我這個頁在做修改的時候,在做checkpoint的時候,我的LSN值是多少(即當時對應的重做日誌的LSN是多少)

LSN是8字節的數字,其單位是字節。每個頁有LSN,重做日誌中也有LSN,Checkpoint 也有LSN

頁中的LSN用來判斷頁是否需要進行恢復操作

例如,頁P1的LSN爲10000,而數據庫啓動時,lnnoDB檢測到寫入重做日誌中的LSN爲13000,並且該事務已經提交,那麼數據庫需要進行恢復操作,將重做日誌應用到P1頁中。

InnoDB存儲引擎在啓動時不管上次數據庫運行時是否正常關閉,都會嘗試進行恢復操作。因爲重做日誌記錄的是物理日誌,因此恢復的速度比邏輯日誌,如二進制日誌要快很多。

看一下存在的幾個LSN

  1. log sequence number:表示當前在內存中重做日誌的LSN的值
  2. log flushed up to:表示寫到磁盤上重做日誌的LSN的值
  3. pages flushed up to:頁刷新的時候,最新的LSN的值,表示checkpoint已經刷新到磁盤頁上的LSN
  4. last checkpoint:頁刷新的時候,最oldest的LSN的值。checkpoint是根據頁的oldest LSN對髒頁列表進行排序的,但是寫入的頁是newest的LSN,所以除非這個頁只改了一次,只有這時oldest=newest,之後都是3>4,其值越大說明需要提升checkpoint的跟進速度

1和2的值可以是不同的,因爲我重做日誌也是先寫在內存裏面,然後再刷新到文件裏面,上面兩個值,一個是內存,一個是磁盤,所以也可能是不一樣的

2=3,說明所有頁都已經刷回磁盤

2-3=髒頁還未刷新到磁盤的大小

fuzzy checkout技術,是將部分髒頁刷新回磁盤,對系統影響較小。

到底是多少髒頁呢?是由這個值來決定的,所以這個參數很重要,值大一點,那麼寫的能力就會提高起來,但如果值很大,那麼可能就會hang住

這個參數是innodb調優需要很關注,但是很容易忽略的一個點

double write技術保證redo log有效

目的:提高數據寫入的可靠性

當發生數據庫宕機時,可能lnnoDB存儲引擎正在寫入某個頁到表中,而這個頁只寫了一部分,比如16KB 的頁,只寫了前4KB,之後就發生了宕機,這種情況被稱爲部分寫失效(partial page write)。在InnoDB 存儲引擎未使用double write技術前,曾經出現過因爲部分寫失效而導致數據丟失的情況。

通過redo log進行恢復,那麼磁盤上的這個頁一定要是完整乾淨的。發生partial page write,磁盤上的這個頁已經corrupt了(非物理介質的corrupt)。比如說:我向redo日誌中寫入一條記錄,對這個頁進行reorganize,但這個頁已經corrupt了,其無法進行reorganize的。

也就是說,在向表中寫一個髒頁之前,一定要有一個地方去記錄這個頁的副本

這就是說,在對磁盤上的某個頁應用(apply)重做日誌前,用戶需要一個頁的副本,當寫入失效發生時,先通過頁的副本在磁盤上還原該頁,再進行重做,這就是double write。

具體做法:

double write 由兩部分組成,一部分是內存中的doublewrite buffer,大小爲2MB,另一部分是物理磁盤上共享表空間(double write段)中連續的128個頁128*16=2M,即2個區(extent),大小同樣爲2MB。

在對緩衝池的髒頁進行刷新時,並不直接寫磁盤,而是會通過memcpy函數將髒頁先複製到內存中的doublewrite buffer,之後通過doublewrite buffer再分兩次,每次1MB順序地寫入共享表空間的物理磁盤上,然後馬上調用fsync 函數,同步磁盤,避免緩衝寫帶來的問題。

在完成doublewrite頁的寫入後,再將doublewrite buffer 中的頁寫入各個表空間文件.ibd文件中,此時的寫入則是離散的,通過space和page_no刷新到.ibd文件中

如果寫入到共享表空間的時候發生partial page write呢?

因爲我磁盤上.ibd文件中的頁還是乾淨的,沒有發生corrupt,數據還是一致的吧,這些頁仍然可以通過redo進行恢復,因爲這個頁仍處於一致的狀態,並沒有不一致。

如果我寫到.ibd文件中發生partial write,那麼在double write這個對象裏面,是不是有這個頁的一個副本,我把對應這個頁的副本(最新的)copy到.ibd文件中,然後再通過redo進行恢復。

即任意一個時刻,總有一個乾淨的頁的存在

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章