溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

MySQL 日志系統(tǒng)中的redolog和binlog的用法

發(fā)布時(shí)間:2021-09-14 11:48:05 來(lái)源:億速云 閱讀:141 作者:柒染 欄目:大數(shù)據(jù)

MySQL 日志系統(tǒng)中的redolog和binlog的用法,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。

之前我們了解了一條查詢語(yǔ)句的執(zhí)行流程,并介紹了執(zhí)行過(guò)程中涉及的處理模塊。一條查詢語(yǔ)句的執(zhí)行過(guò)程一般是經(jīng)過(guò)連接器、分析器、優(yōu)化器、執(zhí)行器等功能模塊,最后到達(dá)存儲(chǔ)引擎。

那么,一條 SQL 更新語(yǔ)句的執(zhí)行流程又是怎樣的呢?

首先我們創(chuàng)建一個(gè)表 user_info,主鍵為 id,創(chuàng)建語(yǔ)句如下:

CREATE TABLE `T` (
  `ID` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

插入一條數(shù)據(jù):

INSERT INTO T VALUES ('2', '1');

如果要將 ID=2 這一行的 c 的值加 1,SQL 語(yǔ)句為:

UPDATE T SET c = c + 1 WHERE ID = 2;

前面介紹過(guò) SQL 語(yǔ)句基本的執(zhí)行鏈路,這里把那張圖拿過(guò)來(lái)。因?yàn)椋抡Z(yǔ)句同樣會(huì)走一遍查詢語(yǔ)句走的流程。

MySQL 日志系統(tǒng)中的redolog和binlog的用法

  1. 通過(guò)連接器,客戶端與 MySQL 建立連接

  2. update 語(yǔ)句會(huì)把 T 表上的所有查詢緩存結(jié)果清空

  3. 分析器會(huì)通過(guò)詞法分析和語(yǔ)法分析識(shí)別這是一條更新語(yǔ)句

  4. 優(yōu)化器會(huì)決定使用 ID 這個(gè)索引(聚簇索引)

  5. 執(zhí)行器負(fù)責(zé)具體執(zhí)行,找到匹配的一行,然后更新

  6. 更新過(guò)程中還會(huì)涉及 redo log(重做日志)和 binlog(歸檔日志)的操作

其中,這兩種日志默認(rèn)在數(shù)據(jù)庫(kù)的 data 目錄下,redo log 是 ib_logfile0 格式的,binlog 是 xxx-bin.000001 格式的。

接下來(lái)讓我們分別去研究下日志模塊中的 redo log 和 binlog。

日志模塊:redo log

在 MySQL 中,如果每一次的更新操作都需要寫(xiě)進(jìn)磁盤(pán),然后磁盤(pán)也要找到對(duì)應(yīng)的那條記錄,然后再更新,整個(gè)過(guò)程 IO 成本、查找成本都很高。為了解決這個(gè)問(wèn)題,MySQL 的設(shè)計(jì)者就采用了日志(redo log)來(lái)提升更新效率。

而日志和磁盤(pán)配合的整個(gè)過(guò)程,其實(shí)就是 MySQL 里的 WAL 技術(shù),WAL 的全稱是 Write-Ahead Logging,它的關(guān)鍵點(diǎn)就是先寫(xiě)日志,再寫(xiě)磁盤(pán)。

具體來(lái)說(shuō),當(dāng)有一條記錄需要更新的時(shí)候,InnoDB 引擎就會(huì)先把記錄寫(xiě)到 redo log(redolog buffer)里面,并更新內(nèi)存(buffer pool),這個(gè)時(shí)候更新就算完成了。同時(shí),InnoDB 引擎會(huì)在適當(dāng)?shù)臅r(shí)候(如系統(tǒng)空閑時(shí)),將這個(gè)操作記錄更新到磁盤(pán)里面(刷臟頁(yè))。

redo log 是 InnoDB 存儲(chǔ)引擎層的日志,又稱重做日志文件,redo log 是循環(huán)寫(xiě)的,redo log 不是記錄數(shù)據(jù)頁(yè)更新之后的狀態(tài),而是記錄這個(gè)頁(yè)做了什么改動(dòng)。

redo log 是固定大小的,比如可以配置為一組 4 個(gè)文件,每個(gè)文件的大小是 1GB,那么日志總共就可以記錄 4GB 的操作。從頭開(kāi)始寫(xiě),寫(xiě)到末尾就又回到開(kāi)頭循環(huán)寫(xiě),如下圖所示。

MySQL 日志系統(tǒng)中的redolog和binlog的用法

圖中展示了一組 4 個(gè)文件的 redo log 日志,checkpoint 是當(dāng)前要擦除的位置,擦除記錄前需要先把對(duì)應(yīng)的數(shù)據(jù)落盤(pán)(更新內(nèi)存頁(yè),等待刷臟頁(yè))。write pos 到 checkpoint 之間的部分可以用來(lái)記錄新的操作,如果 write pos 和 checkpoint 相遇,說(shuō)明 redolog 已滿,這個(gè)時(shí)候數(shù)據(jù)庫(kù)停止進(jìn)行數(shù)據(jù)庫(kù)更新語(yǔ)句的執(zhí)行,轉(zhuǎn)而進(jìn)行 redo log 日志同步到磁盤(pán)中。checkpoint 到 write pos 之間的部分等待落盤(pán)(先更新內(nèi)存頁(yè),然后等待刷臟頁(yè))。

有了 redo log 日志,那么在數(shù)據(jù)庫(kù)進(jìn)行異常重啟的時(shí)候,可以根據(jù) redo log 日志進(jìn)行恢復(fù),也就達(dá)到了 crash-safe。

redo log 用于保證 crash-safe 能力。innodb_flush_log_at_trx_commit 這個(gè)參數(shù)設(shè)置成 1 的時(shí)候,表示每次事務(wù)的 redo log 都直接持久化到磁盤(pán)。這個(gè)參數(shù)建議設(shè)置成 1,這樣可以保證 MySQL 異常重啟之后數(shù)據(jù)不丟失。

日志模塊:binlog

MySQL 整體來(lái)看,其實(shí)就有兩塊:一塊是 Server 層,它主要做的是 MySQL 功能層面的事情;還有一塊是引擎層,負(fù)責(zé)存儲(chǔ)相關(guān)的具體事宜。redo log 是 InnoDB 引擎特有的日志,而 Server 層也有自己的日志,稱為 binlog(歸檔日志)。

binlog 屬于邏輯日志,是以二進(jìn)制的形式記錄的是這個(gè)語(yǔ)句的原始邏輯,依靠 binlog 是沒(méi)有 crash-safe 能力的。

binlog 有兩種模式,statement 格式的話是記 sql 語(yǔ)句,row 格式會(huì)記錄行的內(nèi)容,記兩條,更新前和更新后都有。

sync_binlog 這個(gè)參數(shù)設(shè)置成 1 的時(shí)候,表示每次事務(wù)的 binlog 都持久化到磁盤(pán)。這個(gè)參數(shù)也建議設(shè)置成 1,這樣可以保證 MySQL 異常重啟之后 binlog 不丟失。

為什么會(huì)有兩份日志呢?

因?yàn)樽铋_(kāi)始 MySQL 里并沒(méi)有 InnoDB 引擎。MySQL 自帶的引擎是 MyISAM,但是 MyISAM 沒(méi)有 crash-safe 的能力,binlog 日志只能用于歸檔。而 InnoDB 是另一個(gè)公司以插件形式引入 MySQL 的,既然只依靠 binlog 是沒(méi)有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系統(tǒng)——也就是 redo log 來(lái)實(shí)現(xiàn) crash-safe 能力。

redo log 和 binlog 區(qū)別:

  1. redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實(shí)現(xiàn)的,所有引擎都可以使用。

  2. redo log 是物理日志,記錄的是在某個(gè)數(shù)據(jù)頁(yè)上做了什么修改;binlog 是邏輯日志,記錄的是這個(gè)語(yǔ)句的原始邏輯。

  3. redo log 是循環(huán)寫(xiě)的,空間固定會(huì)用完;binlog 是可以追加寫(xiě)入的。追加寫(xiě)是指 binlog 文件寫(xiě)到一定大小后會(huì)切換到下一個(gè),并不會(huì)覆蓋以前的日志。

有了對(duì)這兩個(gè)日志的概念性理解后,再來(lái)看執(zhí)行器和 InnoDB 引擎在執(zhí)行這個(gè) update 語(yǔ)句時(shí)的內(nèi)部流程。

  1. 執(zhí)行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用樹(shù)搜索找到這一行。如果 ID=2 這一行所在的數(shù)據(jù)頁(yè)本來(lái)就在內(nèi)存中,就直接返回給執(zhí)行器;否則,需要先從磁盤(pán)讀入內(nèi)存,然后再返回。

  2. 執(zhí)行器拿到引擎給的行數(shù)據(jù),把這個(gè)值加上 1,比如原來(lái)是 N,現(xiàn)在就是 N+1,得到新的一行數(shù)據(jù),再調(diào)用引擎接口寫(xiě)入這行新數(shù)據(jù)。

  3. 引擎將這行新數(shù)據(jù)更新到內(nèi)存(InnoDB Buffer Pool)中,同時(shí)將這個(gè)更新操作記錄到 redo log 里面,此時(shí) redo log 處于 prepare 狀態(tài)。然后告知執(zhí)行器執(zhí)行完成了,隨時(shí)可以提交事務(wù)。

  4. 執(zhí)行器生成這個(gè)操作的 binlog,并把 binlog 寫(xiě)入磁盤(pán)。

  5. 執(zhí)行器調(diào)用引擎的提交事務(wù)接口,引擎把剛剛寫(xiě)入的 redo log 改成提交(commit)狀態(tài),更新完成。

下圖為 update 語(yǔ)句的執(zhí)行流程圖,圖中灰色框表示是在 InnoDB 內(nèi)部執(zhí)行的,綠色框表示是在執(zhí)行器中執(zhí)行的。

MySQL 日志系統(tǒng)中的redolog和binlog的用法

其中將 redo log 的寫(xiě)入拆成了兩個(gè)步驟:prepare 和 commit,這就是兩階段提交(2PC)。

兩階段提交(2PC)

MySQL 使用兩階段提交主要解決 binlog 和 redo log 的數(shù)據(jù)一致性的問(wèn)題。

redo log 和 binlog 都可以用于表示事務(wù)的提交狀態(tài),而兩階段提交就是讓這兩個(gè)狀態(tài)保持邏輯上的一致。下圖為 MySQL 二階段提交簡(jiǎn)圖:

MySQL 日志系統(tǒng)中的redolog和binlog的用法

兩階段提交原理描述:

  1. InnoDB redo log 寫(xiě)盤(pán),InnoDB 事務(wù)進(jìn)入 prepare 狀態(tài)。

  2. 如果前面 prepare 成功,binlog 寫(xiě)盤(pán),那么再繼續(xù)將事務(wù)日志持久化到 binlog,如果持久化成功,那么 InnoDB 事務(wù)則進(jìn)入 commit 狀態(tài)(在 redo log 里面寫(xiě)一個(gè) commit 記錄)

備注: 每個(gè)事務(wù) binlog 的末尾,會(huì)記錄一個(gè) XID event,標(biāo)志著事務(wù)是否提交成功,也就是說(shuō),recovery 過(guò)程中,binlog 最后一個(gè) XID event 之后的內(nèi)容都應(yīng)該被 purge。

日志相關(guān)問(wèn)題

怎么進(jìn)行數(shù)據(jù)恢復(fù)?

binlog 會(huì)記錄所有的邏輯操作,并且是采用追加寫(xiě)的形式。當(dāng)需要恢復(fù)到指定的某一秒時(shí),比如今天下午二點(diǎn)發(fā)現(xiàn)中午十二點(diǎn)有一次誤刪表,需要找回?cái)?shù)據(jù),那你可以這么做:

  • 首先,找到最近的一次全量備份,從這個(gè)備份恢復(fù)到臨時(shí)庫(kù)

  • 然后,從備份的時(shí)間點(diǎn)開(kāi)始,將備份的 binlog 依次取出來(lái),重放到中午誤刪表之前的那個(gè)時(shí)刻。

這樣你的臨時(shí)庫(kù)就跟誤刪之前的線上庫(kù)一樣了,然后你可以把表數(shù)據(jù)從臨時(shí)庫(kù)取出來(lái),按需要恢復(fù)到線上庫(kù)去。

redo log 和 binlog 是怎么關(guān)聯(lián)起來(lái)的?

redo log 和 binlog 有一個(gè)共同的數(shù)據(jù)字段,叫 XID。崩潰恢復(fù)的時(shí)候,會(huì)按順序掃描 redo log:

  • 如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;

  • 如果碰到只有 parepare、而沒(méi)有 commit 的 redo log,就拿著 XID 去 binlog 找對(duì)應(yīng)的事務(wù)。

MySQL 怎么知道 binlog 是完整的?

一個(gè)事務(wù)的 binlog 是有完整格式的:

  • statement 格式的 binlog,最后會(huì)有 COMMIT

  • row 格式的 binlog,最后會(huì)有一個(gè) XID event

在 MySQL 5.6.2 版本以后,還引入了 binlog-checksum 參數(shù),用來(lái)驗(yàn)證 binlog 內(nèi)容的正確性。對(duì)于 binlog 日志由于磁盤(pán)原因,可能會(huì)在日志中間出錯(cuò)的情況,MySQL 可以通過(guò)校驗(yàn) checksum 的結(jié)果來(lái)發(fā)現(xiàn)。所以,MySQL 是有辦法驗(yàn)證事務(wù) binlog 的完整性的。

redo log 一般設(shè)置多大?

redo log 太小的話,會(huì)導(dǎo)致很快就被寫(xiě)滿,然后不得不強(qiáng)行刷 redo log,這樣 WAL 機(jī)制的能力就發(fā)揮不出來(lái)了。

如果是幾個(gè) TB 的磁盤(pán)的話,直接將 redo log 設(shè)置為 4 個(gè)文件,每個(gè)文件 1GB。

數(shù)據(jù)寫(xiě)入后的最終落盤(pán),是從 redo log 更新過(guò)來(lái)的還是從 buffer pool 更新過(guò)來(lái)的呢?

實(shí)際上,redo log 并沒(méi)有記錄數(shù)據(jù)頁(yè)的完整數(shù)據(jù),所以它并沒(méi)有能力自己去更新磁盤(pán)數(shù)據(jù)頁(yè),也就不存在由 redo log 更新過(guò)去數(shù)據(jù)最終落盤(pán)的情況。

  1. 數(shù)據(jù)頁(yè)被修改以后,跟磁盤(pán)的數(shù)據(jù)頁(yè)不一致,稱為臟頁(yè)。最終數(shù)據(jù)落盤(pán),就是把內(nèi)存中的數(shù)據(jù)頁(yè)寫(xiě)盤(pán)。這個(gè)過(guò)程與 redo log 毫無(wú)關(guān)系。

  2. 在崩潰恢復(fù)場(chǎng)景中,InnoDB 如果判斷到一個(gè)數(shù)據(jù)頁(yè)可能在崩潰恢復(fù)的時(shí)候丟失了更新,就會(huì)將它讀到內(nèi)存,然后讓 redo log 更新內(nèi)存內(nèi)容。更新完成后,內(nèi)存頁(yè)變成臟頁(yè),就回到了第一種情況的狀態(tài)。

redo log buffer 是什么?是先修改內(nèi)存,還是先寫(xiě) redo log 文件?

在一個(gè)事務(wù)的更新過(guò)程中,日志是要寫(xiě)多次的。比如下面這個(gè)事務(wù):

begin;
INSERT INTO T1 VALUES ('1', '1');
INSERT INTO T2 VALUES ('1', '1');
commit;

這個(gè)事務(wù)要往兩個(gè)表中插入記錄,插入數(shù)據(jù)的過(guò)程中,生成的日志都得先保存起來(lái),但又不能在還沒(méi) commit 的時(shí)候就直接寫(xiě)到 redo log 文件里。

因此就需要 redo log buffer 出場(chǎng)了,它就是一塊內(nèi)存,用來(lái)先存 redo 日志的。也就是說(shuō),在執(zhí)行第一個(gè) insert 的時(shí)候,數(shù)據(jù)的內(nèi)存被修改了,redo log buffer 也寫(xiě)入了日志。

但是,真正把日志寫(xiě)到 redo log 文件,是在執(zhí)行 commit 語(yǔ)句的時(shí)候做的。

以下是我截取的部分 redo log buffer 的源代碼:

/** redo log buffer */
struct log_t{
	char		pad1[CACHE_LINE_SIZE];
	lsn_t		lsn;		
	ulint		buf_free;   // buffer 內(nèi)剩余空間的起始點(diǎn)的 offset
#ifndef UNIV_HOTBACKUP
	char		pad2[CACHE_LINE_SIZE];
	LogSysMutex	mutex;		
	LogSysMutex	write_mutex;	
	char		pad3[CACHE_LINE_SIZE];
	FlushOrderMutex	log_flush_order_mutex;
#endif /* !UNIV_HOTBACKUP */
	byte*		buf_ptr;    // 隱性的 buffer
	byte*		buf;        // 真正操作的 buffer
	bool		first_in_use;	
	ulint		buf_size;   // buffer大小
	bool		check_flush_or_checkpoint;
	UT_LIST_BASE_NODE_T(log_group_t) log_groups;

#ifndef UNIV_HOTBACKUP
	/** The fields involved in the log buffer flush @{ */
	ulint		buf_next_to_write;
	volatile bool	is_extending;	
	lsn_t		write_lsn;	/*!< last written lsn */
	lsn_t		current_flush_lsn;
	lsn_t		flushed_to_disk_lsn;
	ulint		n_pending_flushes;
	os_event_t	flush_event;	
	ulint		n_log_ios;	
	ulint		n_log_ios_old;	
	time_t		last_printout_time;

	/** Fields involved in checkpoints @{ */
	lsn_t		log_group_capacity; 
	lsn_t		max_modified_age_async;
	lsn_t		max_modified_age_sync;
	lsn_t		max_checkpoint_age_async;
	lsn_t		max_checkpoint_age;
	ib_uint64_t	next_checkpoint_no;
	lsn_t		last_checkpoint_lsn;
	lsn_t		next_checkpoint_lsn;
	mtr_buf_t*	append_on_checkpoint;
	ulint		n_pending_checkpoint_writes;
	rw_lock_t	checkpoint_lock;
#endif /* !UNIV_HOTBACKUP */
	byte*		checkpoint_buf_ptr;
	byte*		checkpoint_buf;	
	/* @} */
};

redo log buffer 本質(zhì)上只是一個(gè) byte 數(shù)組,但是為了維護(hù)這個(gè) buffer 還需要設(shè)置很多其他的 meta data,這些 meta data 全部封裝在 log_t 結(jié)構(gòu)體中。

總結(jié)

這篇文章主要介紹了 MySQL 里面最重要的兩個(gè)日志,即物理日志 redo log(重做日志)和邏輯日志 binlog(歸檔日志),還講解了有與日志相關(guān)的一些問(wèn)題。

另外還介紹了與 MySQL 日志系統(tǒng)密切相關(guān)的兩階段提交(2PC),兩階段提交是解決分布式系統(tǒng)的一致性問(wèn)題常用的一個(gè)方案,類似的還有 三階段提交(3PC) 和 PAXOS 算法。

看完上述內(nèi)容,你們掌握MySQL 日志系統(tǒng)中的redolog和binlog的用法的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI