您好,登錄后才能下訂單哦!
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ǔ)句走的流程。
通過(guò)連接器,客戶端與 MySQL 建立連接
update 語(yǔ)句會(huì)把 T 表上的所有查詢緩存結(jié)果清空
分析器會(huì)通過(guò)詞法分析和語(yǔ)法分析識(shí)別這是一條更新語(yǔ)句
優(yōu)化器會(huì)決定使用 ID 這個(gè)索引(聚簇索引)
執(zhí)行器負(fù)責(zé)具體執(zhí)行,找到匹配的一行,然后更新
更新過(guò)程中還會(huì)涉及 redo log(重做日志)和 binlog(歸檔日志)的操作
其中,這兩種日志默認(rèn)在數(shù)據(jù)庫(kù)的 data 目錄下,redo log 是 ib_logfile0 格式的,binlog 是 xxx-bin.000001 格式的。
接下來(lái)讓我們分別去研究下日志模塊中的 redo log 和 binlog。
在 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ě),如下圖所示。
圖中展示了一組 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ù)不丟失。
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ū)別:
redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實(shí)現(xiàn)的,所有引擎都可以使用。
redo log 是物理日志,記錄的是在某個(gè)數(shù)據(jù)頁(yè)上做了什么修改;binlog 是邏輯日志,記錄的是這個(gè)語(yǔ)句的原始邏輯。
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)部流程。
執(zhí)行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用樹(shù)搜索找到這一行。如果 ID=2 這一行所在的數(shù)據(jù)頁(yè)本來(lái)就在內(nèi)存中,就直接返回給執(zhí)行器;否則,需要先從磁盤(pán)讀入內(nèi)存,然后再返回。
執(zhí)行器拿到引擎給的行數(shù)據(jù),把這個(gè)值加上 1,比如原來(lái)是 N,現(xiàn)在就是 N+1,得到新的一行數(shù)據(jù),再調(diào)用引擎接口寫(xiě)入這行新數(shù)據(jù)。
引擎將這行新數(shù)據(jù)更新到內(nèi)存(InnoDB Buffer Pool)中,同時(shí)將這個(gè)更新操作記錄到 redo log 里面,此時(shí) redo log 處于 prepare 狀態(tài)。然后告知執(zhí)行器執(zhí)行完成了,隨時(shí)可以提交事務(wù)。
執(zhí)行器生成這個(gè)操作的 binlog,并把 binlog 寫(xiě)入磁盤(pán)。
執(zhí)行器調(diào)用引擎的提交事務(wù)接口,引擎把剛剛寫(xiě)入的 redo log 改成提交(commit)狀態(tài),更新完成。
下圖為 update 語(yǔ)句的執(zhí)行流程圖,圖中灰色框表示是在 InnoDB 內(nèi)部執(zhí)行的,綠色框表示是在執(zhí)行器中執(zhí)行的。
其中將 redo log 的寫(xiě)入拆成了兩個(gè)步驟:prepare 和 commit,這就是兩階段提交(2PC)。
MySQL 使用兩階段提交主要解決 binlog 和 redo log 的數(shù)據(jù)一致性的問(wèn)題。
redo log 和 binlog 都可以用于表示事務(wù)的提交狀態(tài),而兩階段提交就是讓這兩個(gè)狀態(tài)保持邏輯上的一致。下圖為 MySQL 二階段提交簡(jiǎn)圖:
兩階段提交原理描述:
InnoDB redo log 寫(xiě)盤(pán),InnoDB 事務(wù)進(jìn)入 prepare 狀態(tài)。
如果前面 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。
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 有一個(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ù)。
一個(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 太小的話,會(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í)際上,redo log 并沒(méi)有記錄數(shù)據(jù)頁(yè)的完整數(shù)據(jù),所以它并沒(méi)有能力自己去更新磁盤(pán)數(shù)據(jù)頁(yè),也就不存在由 redo log 更新過(guò)去數(shù)據(jù)最終落盤(pán)的情況。
數(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)系。
在崩潰恢復(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)。
在一個(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)體中。
這篇文章主要介紹了 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è)資訊頻道,感謝各位的閱讀!
免責(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)容。