您的位置:首頁 > 軟件教程 > 教程 > Mysql篇-三大日志

Mysql篇-三大日志

來源:好特整理 | 時間:2024-11-14 10:17:27 | 閱讀:125 |  標(biāo)簽: S   | 分享到:

概述 undo log(回滾日志):是 Innodb 存儲引擎層生成的日志,實現(xiàn)了事務(wù)中的原子性,主要用于事務(wù)回滾和 MVCC。 redo log(重做日志):是 Innodb 存儲引擎層生成的日志,實現(xiàn)了事務(wù)中的持久性,主要用于掉電等故障恢復(fù); binlog (歸檔日志):是 Server 層生成

概述

  • undo log(回滾日志):是 Innodb 存儲引擎層生成的日志,實現(xiàn)了事務(wù)中的 原子性 ,主要用于事務(wù)回滾和 MVCC。
  • redo log(重做日志):是 Innodb 存儲引擎層生成的日志,實現(xiàn)了事務(wù)中的 持久性 ,主要用于掉電等故障恢復(fù);
  • binlog (歸檔日志):是 Server 層生成的日志,主要用于數(shù)據(jù)備份和主從復(fù)制;

回滾日志(undo log)

作用

  • 保存了事務(wù)發(fā)生之前的數(shù)據(jù)的一個版本,可以用于回滾,保障原子性
  • 實現(xiàn)多版本并發(fā)控制下的讀(MVCC)的關(guān)鍵因素之一,也即非鎖定讀,MVCC通過Read View + undolog的版本鏈實現(xiàn),可以具體看 MVCC的快照讀

內(nèi)容

邏輯格式的日志,在執(zhí)行 undo 的時候,僅僅是將數(shù)據(jù)從邏輯上恢復(fù)至事務(wù)之前的狀態(tài),而不是從物理頁面上操作實現(xiàn)的,這一點是不同于redo log 的。

每當(dāng) InnoDB 引擎對一條記錄進(jìn)行操作(修改、刪除、新增)時,要把回滾時需要的信息都記錄到 undo log 里,比如:

  • 插入insert 一條記錄時,要把這條記錄的主鍵值記下來,這樣之后回滾時只需要把這個主鍵值對應(yīng)的 記錄刪掉delete 就好了;
  • 刪除delete 一條記錄時,要把這條記錄中的內(nèi)容都記下來,這樣之后回滾時再把由這些內(nèi)容組成的 記錄插入insert 到表中就好了;
  • 更新 一條記錄時,要把被更新的列的舊值記下來,這樣之后回滾時再把這些列 更新為舊值 就好了。

什么時候產(chǎn)生

事務(wù)開始之前 ,MySQL 會先記錄更新前的數(shù)據(jù)到 undo log 日志文件里面,當(dāng)事務(wù)回滾時,可以利用 undo log 來進(jìn)行回滾。同時undo 也會產(chǎn)生 redo 來保證undo log的可靠性。

Mysql篇-三大日志

什么時候刷盤

undo log 和數(shù)據(jù)頁的刷盤策略是一樣的,都需要通過 redo log 保證持久化。產(chǎn)生undo日志的時候,同樣會伴隨類似于保護(hù)事務(wù)持久化機(jī)制的redolog的產(chǎn)生。

buffer pool 中有 undo 頁,對 undo 頁的修改也都會記錄到 redo log。redo log 會每秒刷盤,提交事務(wù)時也會刷盤,數(shù)據(jù)頁和 undo 頁都是靠這個機(jī)制保證持久化的,具體看下面內(nèi)容。

重做日志(redo log)

作用

  • 確保事務(wù)的持久性。
    • 為了防止斷電導(dǎo)致數(shù)據(jù)丟失的問題,當(dāng)有一條記錄需要更新的時候,InnoDB 引擎就會先更新內(nèi)存(同時標(biāo)記為臟頁),然后將本次對這個頁的修改以 redo log 的形式記錄下來,這個時候更新就算完成了。也就是說, redo log 是為了防止 Buffer Pool 中的臟頁丟失而設(shè)計的。
    • 在重啟mysql服務(wù)的時候,根據(jù)redo log進(jìn)行重做,從而達(dá)到事務(wù)的持久性這一特性。
  • 將寫操作從「隨機(jī)寫」變成了「順序?qū)憽,提?MySQL 寫入磁盤的性能。

內(nèi)容

物理格式的日志,記錄的是物理數(shù)據(jù)頁面的修改的信息,其 redo log 是順序?qū)懭雛edo log file 的物理文件中去的。同時,在內(nèi)存修改 Undo log 后,也需要記錄undo log對應(yīng)的 redo log。

redo log 和 undo log 區(qū)別:

  • redo log 記錄了此次事務(wù) 完成后 的數(shù)據(jù)狀態(tài),記錄的是更新之后的值;
  • undo log 記錄了此次事務(wù) 開始前 的數(shù)據(jù)狀態(tài),記錄的是更新之前的值;

什么時候產(chǎn)生

事務(wù)開始之后就產(chǎn)生redo log,redo log的落盤并不是隨著事務(wù)的提交才寫入的,而是在事務(wù)的執(zhí)行過程中,便開始寫入redo log文件中。

事務(wù)提交之前發(fā)生了崩潰,重啟后會通過 undo log 回滾事務(wù),事務(wù)提交之后發(fā)生了崩潰,重啟后會通過 redo log 恢復(fù)事務(wù),如下圖:
Mysql篇-三大日志

redo log 要寫到磁盤,數(shù)據(jù)也要寫磁盤,為什么要多此一舉?

寫入 redo log 的方式使用了追加操作, 所以磁盤操作是順序?qū),而寫入?shù)據(jù)需要先找到寫入位置,然后才寫到磁盤,所以磁盤操作是隨機(jī)寫。磁盤的「順序?qū)?」比「隨機(jī)寫」 高效的多,因此 redo log 寫入磁盤的開銷更小。

什么時候刷盤

實際上, 執(zhí)行一個事務(wù)的過程中,產(chǎn)生的 redo log 也不是直接寫入磁盤的,因為這樣會產(chǎn)生大量的 I/O 操作,而且磁盤的運(yùn)行速度遠(yuǎn)慢于內(nèi)存。

redo log有一個緩存區(qū) Innodb_log_buffer,Innodb_log_buffer 的默認(rèn)大小為 16M,每當(dāng)產(chǎn)生一條 redo log 時,會先寫入到 redo log buffer,后續(xù)再持久化到磁盤。

Mysql篇-三大日志

然后會通過以下三種方式將innodb log buffer的日志刷新到磁盤:

  • MySQL 正常關(guān)閉時;
  • 當(dāng) redo log buffer 中記錄的寫入量大于 redo log buffer 內(nèi)存空間的一半時,會觸發(fā)落盤;
  • InnoDB 的后臺線程每隔 1 秒,將 redo log buffer 持久化到磁盤。
  • 每次事務(wù)提交時都將緩存在 redo log buffer 里的 redo log 直接持久化到磁盤。

因此redo log buffer的寫盤,并不一定是隨著事務(wù)的提交才寫入redo log文件的,而是隨著事務(wù)的開始,逐步開始的。

即使某個事務(wù)還沒有提交,Innodb存儲引擎仍然每秒會將redo log buffer刷新到redo log文件。

這一點是必須要知道的,因為這可以很好地解釋再大的事務(wù)的提交(commit)的時間也是很短暫的。

redolog的文件

兩個 redo 日志的文件名叫 :ib_logfile0 和 ib_logfile1。

redo log文件組是以循環(huán)寫的方式工作的, InnoDB 存儲引擎會先寫 ib_logfile0 文件,當(dāng) ib_logfile0 文件被寫滿的時候,會切換至 ib_logfile1 文件,當(dāng) ib_logfile1 文件也被寫滿時,會切換回 ib_logfile0 文件;相當(dāng)于一個環(huán)形。

Mysql篇-三大日志

  • write pos 和 checkpoint 的移動都是順時針方向;
  • write pos ~ checkpoint 之間的部分(圖中的紅色部分),用來記錄新的更新操作;
  • check point ~ write pos 之間的部分(圖中藍(lán)色部分):待落盤的臟數(shù)據(jù)頁記錄;

因此,如果 write pos 追上了 checkpoint,就意味著 redo log 文件滿了,這時 MySQL 不能再執(zhí)行新的更新操作,也就是說 MySQL 會被阻塞

二進(jìn)制日志(binlog)

作用

  • 用于復(fù)制,在主從復(fù)制中,從庫利用主庫上的binlog進(jìn)行重放,實現(xiàn)主從同步。
  • 用于數(shù)據(jù)庫的基于時間點的還原,即備份恢復(fù)

內(nèi)容

binlog 有 3 種格式類型,分別是 STATEMENT(默認(rèn)格式)、ROW、 MIXED,區(qū)別如下:

  • STATEMENT:每一條修改數(shù)據(jù)的 SQL 都會被記錄到 binlog 中(相當(dāng)于記錄了邏輯操作,所以針對這種格式, binlog 可以稱為邏輯日志),主從復(fù)制中 slave 端再根據(jù) SQL 語句重現(xiàn)。但 STATEMENT 有動態(tài)函數(shù)的問題,比如你用了 uuid 或者 now 這些函數(shù),你在主庫上執(zhí)行的結(jié)果并不是你在從庫執(zhí)行的結(jié)果,這種隨時在變的函數(shù)會導(dǎo)致復(fù)制的數(shù)據(jù)不一致;
  • ROW:記錄行數(shù)據(jù)最終被修改成什么樣了(這種格式的日志,就不能稱為邏輯日志了),不會出現(xiàn) STATEMENT 下動態(tài)函數(shù)的問題。但 ROW 的缺點是每行數(shù)據(jù)的變化結(jié)果都會被記錄,比如執(zhí)行批量 update 語句,更新多少行數(shù)據(jù)就會產(chǎn)生多少條記錄,使 binlog 文件過大,而在 STATEMENT 格式下只會記錄一個 update 語句而已;
  • MIXED:包含了 STATEMENT 和 ROW 模式,它會根據(jù)不同的情況自動使用 ROW 模式和 STATEMENT 模式;

注意:不同的日志類型在主從復(fù)制下除了有動態(tài)函數(shù)的問題,同樣對對更新時間也有影響。一般來說,數(shù)據(jù)庫中的update_time都會設(shè)置成ON UPDATE CURRENT_TIMESTAMP,即自動更新時間戳列。在主從復(fù)制下,
如果日志格式類型是STATEMENT,由于記錄的是sql語句,在salve端是進(jìn)行語句重放,那么更新時間也是重放時的時間,此時slave會有時間延遲的問題;
如果日志格式類型是ROW,這是記錄行數(shù)據(jù)最終被修改成什么樣了,這種從庫的數(shù)據(jù)是與主服務(wù)器完全一致的。

什么時候產(chǎn)生

事務(wù) 提交的時候 ,一次性將事務(wù)中的sql語句(一個事物可能對應(yīng)多個sql語句)按照一定的格式記錄到binlog中。

binlog 文件是記錄了所有數(shù)據(jù)庫表結(jié)構(gòu)變更和表數(shù)據(jù)修改的日志,不會記錄查詢類的操作,比如 SELECT 和 SHOW 操作。

這里與redo log很明顯的差異就是binlog 是追加寫,寫滿一個文件,就創(chuàng)建一個新的文件繼續(xù)寫,不會覆蓋以前的日志,保存的是全量的日志。redo log 是循環(huán)寫,日志空間大小是固定,全部寫滿就從頭開始,保存未被刷入磁盤的臟頁日志。

也就是說,如果不小心整個數(shù)據(jù)庫的數(shù)據(jù)被刪除了,只能使用 bin log 文件恢復(fù)數(shù)據(jù)。因為redo log循環(huán)寫會擦除數(shù)據(jù)。

主從復(fù)制的實現(xiàn)

MySQL 的主從復(fù)制依賴于 binlog ,也就是記錄 MySQL 上的所有變化并以二進(jìn)制形式保存在磁盤上。復(fù)制的過程就是將 binlog 中的數(shù)據(jù)從主庫傳輸?shù)綇膸焐稀?

這個過程一般是異步的,也就是主庫上執(zhí)行事務(wù)操作的線程不會等待復(fù)制 binlog 的線程同步完成。
Mysql篇-三大日志

MySQL 集群的主從復(fù)制過程如下:

  • 寫入 Binlog:MySQL 主庫在收到客戶端提交事務(wù)的請求之后,會先寫入 binlog,再提交事務(wù),更新存儲引擎中的數(shù)據(jù),事務(wù)提交完成后,返回給客戶端“操作成功”的響應(yīng)。
  • 同步 Binlog:從庫會創(chuàng)建一個專門的 I/O 線程,連接主庫的 log dump 線程,來接收主庫的 binlog 日志,再把 binlog 信息寫入 relay log 的中繼日志里,再返回給主庫“復(fù)制成功”的響應(yīng)。
  • 回放 Binlog:從庫會創(chuàng)建一個用于回放 binlog 的線程,去讀 relay log 中繼日志,然后回放 binlog 更新存儲引擎中的數(shù)據(jù),最終實現(xiàn)主從的數(shù)據(jù)一致性。

什么時候刷盤

在刷盤時機(jī)上與redolog不一樣,redolog即使事務(wù)沒提交,也可以每隔1秒就刷盤。但是一個事務(wù)的 binlog 是不能被拆開的,因此無論這個事務(wù)有多大(比如有很多條語句),也要保證一次性寫入。如果一個事務(wù)的 binlog 被拆開的時候,在備庫執(zhí)行就會被當(dāng)做多個事務(wù)分段自行,這樣就破壞了原子性,是有問題的。

bin log日志與redo log類似,也有對應(yīng)的緩存,叫 binlog cache。事務(wù)提交的時候,再把 binlog cache 寫到 binlog 文件中。
Mysql篇-三大日志

  • 圖中的 write,指的就是指把日志寫入到 binlog 文件,但是并沒有把數(shù)據(jù)持久化到磁盤,因為數(shù)據(jù)還緩存在文件系統(tǒng)的 page cache 里,write 的寫入速度還是比較快的,因為不涉及磁盤 I/O。
  • 圖中的 fsync,才是將數(shù)據(jù)持久化到磁盤的操作,這里就會涉及磁盤 I/O,所以頻繁的 fsync 會導(dǎo)致磁盤的 I/O 升高。

MySQL提供一個 sync_binlog 參數(shù)來控制數(shù)據(jù)庫的 binlog 刷到磁盤上的頻率:

  • sync_binlog = 0 的時候,表示每次提交事務(wù)都只 write,不 fsync,后續(xù)交由操作系統(tǒng)決定何時將數(shù)據(jù)持久化到磁盤;
  • sync_binlog = 1 的時候,表示每次提交事務(wù)都會 write,然后馬上執(zhí)行 fsync;
  • sync_binlog =N(N>1) 的時候,表示每次提交事務(wù)都 write,但累積 N 個事務(wù)后才 fsync。

顯然,在MySQL中系統(tǒng)默認(rèn)的設(shè)置是 sync_binlog = 0,也就是不做任何強(qiáng)制性的磁盤刷新指令,這時候的性能是最好的,但是風(fēng)險也是最大的。因為一旦主機(jī)發(fā)生異常重啟,還沒持久化到磁盤的數(shù)據(jù)就會丟失。

而當(dāng) sync_binlog 設(shè)置為 1 的時候,是最安全但是性能損耗最大的設(shè)置。因為當(dāng)設(shè)置為 1 的時候,即使主機(jī)發(fā)生異常重啟,最多丟失一個事務(wù)的 binlog,而已經(jīng)持久化到磁盤的數(shù)據(jù)就不會有影響,不過就是對寫入性能影響太大。

如果能容少量事務(wù)的 binlog 日志丟失的風(fēng)險,為了提高寫入的性能,一般會 sync_binlog 設(shè)置為 100~1000 中的某個數(shù)值。

兩階段提交

事務(wù)提交后,redo log 和 binlog 都要持久化到磁盤,但是這兩個是獨立的邏輯,可能出現(xiàn)半成功的狀態(tài),這樣就造成兩份日志之間的邏輯不一致。如下:

  1. 如果在將 redo log 刷入到磁盤之后, MySQL 突然宕機(jī)了,而 binlog 還沒有來得及寫入。那么機(jī)器重啟后,這臺機(jī)器會通過redo log恢復(fù)數(shù)據(jù),但是這個時候binlog并沒有記錄該數(shù)據(jù),后續(xù)進(jìn)行機(jī)器備份的時候,就會丟失這一條數(shù)據(jù),同時主從同步也會丟失這一條數(shù)據(jù)。
  2. 如果在將 binlog 刷入到磁盤之后, MySQL 突然宕機(jī)了,而 redo log 還沒有來得及寫入。由于 redo log 還沒寫,崩潰恢復(fù)以后這個事務(wù)無效,而 binlog 里面記錄了這條更新語句,在主從架構(gòu)中,binlog 會被復(fù)制到從庫,從庫執(zhí)行了這條更新語句,那么就與主庫的值不一致性;

兩階段提交把單個事務(wù)的提交拆分成了 2 個階段,分別是「準(zhǔn)備(Prepare)階段」和「提交(Commit)階段」

具體過程

Mysql篇-三大日志
事務(wù)的提交過程有兩個階段,就是將 redo log 的寫入拆成了兩個步驟:prepare 和 commit,中間再穿插寫入binlog,具體如下:

  • prepare 階段:將 XID(內(nèi)部 XA 事務(wù)的 ID) 寫入到 redo log,同時將 redo log 對應(yīng)的事務(wù)狀態(tài)設(shè)置為 prepare,然后將 redo log 持久化到磁盤(innodb_flush_log_at_trx_commit = 1 的作用);
  • commit 階段:把 XID 寫入到 binlog,然后將 binlog 持久化到磁盤(sync_binlog = 1 的作用),接著調(diào)用引擎的提交事務(wù)接口,將 redo log 狀態(tài)設(shè)置為 commit,此時該狀態(tài)并不需要持久化到磁盤,只需要 write 到文件系統(tǒng)的 page cache 中就夠了,因為只要 binlog 寫磁盤成功,就算 redo log 的狀態(tài)還是 prepare 也沒有關(guān)系,一樣會被認(rèn)為事務(wù)已經(jīng)執(zhí)行成功;

總的來說就是,事務(wù)提交后,redo log變成prepare 階段,再寫入binlog,返回成功后redo log 進(jìn)入commit 階段。

總結(jié)三個日志的具體流程

當(dāng)優(yōu)化器分析出成本最小的執(zhí)行計劃后,執(zhí)行器就按照執(zhí)行計劃開始進(jìn)行更新操作。

具體更新一條記錄 UPDATE t_user SET name = 'xiaolin' WHERE id = 1; 的流程如下:

  1. 檢查在不在buffer Pool。 執(zhí)行器負(fù)責(zé)具體執(zhí)行,會調(diào)用存儲引擎的接口,通過主鍵索引樹搜索獲取 id = 1 這一行記錄:
    • 如果 id=1 這一行所在的數(shù)據(jù)頁本來就在 buffer pool 中,就直接返回給執(zhí)行器更新;
    • 如果記錄不在 buffer pool,將數(shù)據(jù)頁從磁盤讀入到 buffer pool,返回記錄給執(zhí)行器。
  2. 檢查是否已經(jīng)是要更新的值。執(zhí)行器得到聚簇索引記錄后,會看一下更新前的記錄和更新后的記錄是否一樣:
    • 如果一樣的話就不進(jìn)行后續(xù)更新流程;
    • 如果不一樣的話就把更新前的記錄和更新后的記錄都當(dāng)作參數(shù)傳給 InnoDB 層,讓 InnoDB 真正的執(zhí)行更新記錄的操作;
  3. 開啟事務(wù),記錄undo log,并記錄修改undo log對應(yīng)的redo log:開啟事務(wù), InnoDB 層更新記錄前,首先要記錄相應(yīng)的 undo log,因為這是更新操作,需要把被更新的列的舊值記下來,也就是要生成一條 undo log,undo log 會寫入 Buffer Pool 中的 Undo 頁面,不過在內(nèi)存修改該 Undo 頁面后,需要記錄對應(yīng)的 redo log。
  4. 標(biāo)記為臟頁,并寫入redo log:InnoDB 層開始更新記錄,會先更新內(nèi)存(同時標(biāo)記為臟頁),然后將記錄寫到 redo log 里面,這個時候更新就算完成了。為了減少磁盤I/O,不會立即將臟頁寫入磁盤,后續(xù)由后臺線程選擇一個合適的時機(jī)將臟頁寫入到磁盤。這就是 WAL 技術(shù),MySQL 的寫操作并不是立刻寫到磁盤上,而是先寫 redo 日志,然后在合適的時間再將修改的行數(shù)據(jù)寫到磁盤上。
  5. 至此,一條記錄更新完了。
  6. 記錄binlog:在一條更新語句執(zhí)行完成后,然后開始記錄該語句對應(yīng)的 binlog,此時記錄的 binlog 會被保存到 binlog cache,并沒有刷新到硬盤上的 binlog 文件,在事務(wù)提交時才會統(tǒng)一將該事務(wù)運(yùn)行過程中的所有 binlog 刷新到硬盤。
  7. 事務(wù)提交,redo log和binlog刷盤。

面試題專欄

Java面試題專欄 已上線,歡迎訪問。

  • 如果你不知道簡歷怎么寫,簡歷項目不知道怎么包裝;
  • 如果簡歷中有些內(nèi)容你不知道該不該寫上去;
  • 如果有些綜合性問題你不知道怎么答;

那么可以私信我,我會盡我所能幫助你。

小編推薦閱讀

好特網(wǎng)發(fā)布此文僅為傳遞信息,不代表好特網(wǎng)認(rèn)同期限觀點或證實其描述。

相關(guān)視頻攻略

更多

掃二維碼進(jìn)入好特網(wǎng)手機(jī)版本!

掃二維碼進(jìn)入好特網(wǎng)微信公眾號!

本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請發(fā)郵件[email protected]

湘ICP備2022002427號-10 湘公網(wǎng)安備:43070202000427號© 2013~2024 haote.com 好特網(wǎng)