自己做淘客網(wǎng)站成本大嗎廣告公司怎么找客戶資源
前言
SQL執(zhí)行流程圖文分析:從連接到執(zhí)行的全貌_一條 sql 執(zhí)行的全流程?-CSDN博客文章瀏覽閱讀1.1k次,點贊20次,收藏12次。本文探討 MySQL 執(zhí)行一條 SQL 查詢語句的詳細(xì)流程,從連接器開始,逐步介紹了查詢緩存、解析 SQL、執(zhí)行計劃優(yōu)化以及執(zhí)行器等各個功能模塊的作用。同時,還解釋了 MySQL 的內(nèi)部架構(gòu),包括 Server 層和存儲引擎層,并討論了短連接與長連接、索引下推等相關(guān)概念。通過本文,讀者能夠全面了解 MySQL 在執(zhí)行 SQL 查詢時的內(nèi)部工作原理,以及如何優(yōu)化查詢性能。_一條 sql 執(zhí)行的全流程?https://blog.csdn.net/weixin_73077810/article/details/137524227以上文章是針對查詢操作的流程剖析,這一套流程,更新語句也是同樣會走一遍:不過,更新語句的流程會涉及到 undo log、redo log 、binlog 這三種日志:
-
undo log:是 Innodb 存儲引擎層生成的日志,實現(xiàn)了事務(wù)中的原子性,主要用于事務(wù)回滾和 MVCC。
-
redo log:是 Innodb 存儲引擎層生成的日志,實現(xiàn)了事務(wù)中的持久性,主要用于掉電等故障恢復(fù);
-
binlog :是 Server 層生成的日志,主要用于數(shù)據(jù)備份和主從復(fù)制;
下面就讓我們好好深入的來學(xué)習(xí)一下這三類日志的細(xì)節(jié),促進(jìn)對于事務(wù)型SQL的執(zhí)行過程的理解、
undo log
????????undo log 是一種用于撤銷回退的日志。在事務(wù)沒提交之前,MySQL 會先記錄更新前的數(shù)據(jù)到 undo log 日志文件里面,當(dāng)事務(wù)回滾時,可以利用 undo log 來進(jìn)行回滾。另外,undo log 還有一個作用,通過 ReadView + undo log 實現(xiàn) MVCC(多版本并發(fā)控制)。
redo log
????????為了防止斷電導(dǎo)致數(shù)據(jù)丟失的問題,當(dāng)有一條記錄需要更新的時候,InnoDB 引擎就會先更新內(nèi)存(同時標(biāo)記為臟頁),然后將本次對這個頁的修改以 redo log 的形式記錄下來,這個時候更新就算完成了。MySQL 的寫操作并不是立刻寫到磁盤上,而是先寫日志,然后在合適的時間再寫到磁盤上。在事務(wù)提交時,只要先將 redo log 持久化到磁盤即可,可以不需要等到將緩存在 Buffer Pool 里的臟頁數(shù)據(jù)持久化到磁盤。當(dāng)系統(tǒng)崩潰時,雖然臟頁數(shù)據(jù)沒有持久化,但是 redo log 已經(jīng)持久化,接著 MySQL 重啟后,可以根據(jù) redo log 的內(nèi)容,將所有數(shù)據(jù)恢復(fù)到最新的狀態(tài)。
redo log 要寫到磁盤,數(shù)據(jù)也要寫磁盤,為什么要多此一舉?
????????寫入 redo log 的方式使用了追加操作, 所以磁盤操作是順序?qū)?/strong>,而寫入數(shù)據(jù)需要先找到寫入位置,然后才寫到磁盤,所以磁盤操作是隨機寫。磁盤的「順序?qū)?」比「隨機寫」 高效的多,因此 redo log 寫入磁盤的開銷更小。
產(chǎn)生的 redo log 是直接寫入磁盤的嗎?
不是的。實際上, 執(zhí)行一個事務(wù)的過程中,產(chǎn)生的 redo log 也不是直接寫入磁盤的,因為這樣會產(chǎn)生大量的 I/O 操作,而且磁盤的運行速度遠(yuǎn)慢于內(nèi)存。
redo log 什么時候刷盤?
主要有下面幾個時機:
-
MySQL 正常關(guān)閉時;
-
當(dāng) redo log buffer 中記錄的寫入量大于 redo log buffer 內(nèi)存空間的一半時,會觸發(fā)落盤;
-
InnoDB 的后臺線程每隔 1 秒,將 redo log buffer 持久化到磁盤。
redo log 文件寫滿了怎么辦?
????????默認(rèn)情況下, InnoDB 存儲引擎有 1 個重做日志文件組,「重做日志文件組」由有 2 個 redo log 文件組成,每個 redo log File 的大小是固定且一致的,重做日志文件組是以循環(huán)寫的方式工作的,從頭開始寫,寫到末尾就又回到開頭,相當(dāng)于一個環(huán)形。
所以 InnoDB 存儲引擎會先寫 ib_logfile0 文件,當(dāng) ib_logfile0 文件被寫滿的時候,會切換至 ib_logfile1 文件,當(dāng) ib_logfile1 文件也被寫滿時,會切換回 ib_logfile0 文件。
redo log 是循環(huán)寫的方式,相當(dāng)于一個環(huán)形,InnoDB 用 write pos 表示 redo log 當(dāng)前記錄寫到的位置,用 checkpoint 表示當(dāng)前要擦除的位置,如下圖:
????????如果 write pos 追上了 checkpoint,就意味著 redo log 文件滿了,這時 MySQL 不能再執(zhí)行新的更新操作,也就是說 MySQL 會被阻塞,此時會停下來將 Buffer Pool 中的臟頁刷新到磁盤中,然后標(biāo)記 redo log 哪些記錄可以被擦除,接著對舊的 redo log 記錄進(jìn)行擦除,等擦除完舊記錄騰出了空間,checkpoint 就會往后移動(圖中順時針),然后 MySQL 恢復(fù)正常運行,繼續(xù)執(zhí)行新的更新操作。
binlog
????????MySQL 在完成一條更新操作后,Server 層還會生成一條 binlog寫到 binlog cache(Server 層的 cache),等之后事務(wù)提交的時候,再把 binlog cache 寫到 binlog 文件中。在此期間,事務(wù)中的binlog如果超過了binlog cache的大小,就要暫存到磁盤。雖然每個線程有自己 binlog cache,但是最終都寫到同一個 binlog 文件
binlog 文件是記錄了所有數(shù)據(jù)庫表結(jié)構(gòu)變更和表數(shù)據(jù)修改的日志,不會記錄查詢類的操作,比如 SELECT 和 SHOW 操作。
如果不小心整個數(shù)據(jù)庫的數(shù)據(jù)被刪除了,能使用 redo log 文件恢復(fù)數(shù)據(jù)嗎?
????????不可以使用 redo log 文件恢復(fù),只能使用 binlog 文件恢復(fù)。因為 redo log 文件是循環(huán)寫,是會邊寫邊擦除日志的,只記錄未被刷入磁盤的數(shù)據(jù)的物理日志,已經(jīng)刷入磁盤的數(shù)據(jù)都會從 redo log 文件里擦除。
????????binlog 文件保存的是全量的日志,也就是保存了所有數(shù)據(jù)變更的情況,理論上只要記錄在 binlog 上的數(shù)據(jù),都可以恢復(fù),所以如果不小心整個數(shù)據(jù)庫的數(shù)據(jù)被刪除了,得用 binlog 文件恢復(fù)數(shù)據(jù)。
兩階段提交
????????事務(wù)提交后,redo log 和 binlog 都要持久化到磁盤,但是這兩個是獨立的邏輯,可能出現(xiàn)半成功的狀態(tài),這樣就造成兩份日志之間的邏輯不一致。如果出現(xiàn)半成功狀態(tài),就會造成主從環(huán)境的數(shù)據(jù)不一致性。這是因為 redo log 影響主庫的數(shù)據(jù),binlog 影響從庫的數(shù)據(jù),所以 redo log 和 binlog 必須保持一致才能保證主從數(shù)據(jù)一致。
????????MySQL 為了避免出現(xiàn)兩份日志之間的邏輯不一致的問題,使用了「兩階段提交」來解決,兩階段提交其實是分布式事務(wù)一致性協(xié)議,它可以保證多個邏輯操作要不全部成功,要不全部失敗,不會出現(xiàn)半成功的狀態(tài)。
????????兩階段提交把單個事務(wù)的提交拆分成了 2 個階段,分別是「準(zhǔn)備(Prepare)階段」和「提交(Commit)階段」,每個階段都由協(xié)調(diào)者和參與者共同完成。注意,不要把提交階段和 commit 語句混淆了,commit 語句執(zhí)行的時候,會包含提交階段。
舉個拳擊比賽的例子,兩位拳擊手(參與者)開始比賽之前,裁判(協(xié)調(diào)者)會在中間確認(rèn)兩位拳擊手的狀態(tài),類似于問你準(zhǔn)備好了嗎?
-
準(zhǔn)備階段:裁判(協(xié)調(diào)者)會依次詢問兩位拳擊手(參與者)是否準(zhǔn)備好了,然后拳擊手聽到后做出應(yīng)答,如果覺得自己準(zhǔn)備好了,就會跟裁判說準(zhǔn)備好了;如果沒有自己還沒有準(zhǔn)備好(比如拳套還沒有帶好),就會跟裁判說還沒準(zhǔn)備好。
-
提交階段:如果兩位拳擊手(參與者)都回答準(zhǔn)備好了,裁判(協(xié)調(diào)者)宣布比賽正式開始,兩位拳擊手就可以直接開打;如果任何一位拳擊手(參與者)回答沒有準(zhǔn)備好,裁判(協(xié)調(diào)者)會宣布比賽暫停,對應(yīng)事務(wù)中的回滾操作。
兩階段提交的過程是怎樣的?
為了保證這兩個日志的一致性,MySQL 使用了內(nèi)部 XA 事務(wù),內(nèi)部 XA 事務(wù)由 binlog 作為協(xié)調(diào)者,存儲引擎是參與者。
當(dāng)客戶端執(zhí)行 commit 語句或者在自動提交的情況下,MySQL 內(nèi)部開啟一個 XA 事務(wù),分兩階段來完成 XA 事務(wù)的提交,如下圖:
-
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í)行成功;
在兩階段提交的不同時刻,MySQL 異常重啟會出現(xiàn)什么現(xiàn)象?
不管是時刻 A(redo log 已經(jīng)寫入磁盤, binlog 還沒寫入磁盤),還是時刻 B (redo log 和 binlog 都已經(jīng)寫入磁盤,還沒寫入 commit 標(biāo)識)崩潰,此時的 redo log 都處于 prepare 狀態(tài)。
在 MySQL 重啟后會按順序掃描 redo log 文件,碰到處于 prepare 狀態(tài)的 redo log,就拿著 redo log 中的 XID 去 binlog 查看是否存在此 XID:
-
如果 binlog 中沒有當(dāng)前內(nèi)部 XA 事務(wù)的 XID,說明 redolog 完成刷盤,但是 binlog 還沒有刷盤,則回滾事務(wù)。對應(yīng)時刻 A 崩潰恢復(fù)的情況。
-
如果 binlog 中有當(dāng)前內(nèi)部 XA 事務(wù)的 XID,說明 redolog 和 binlog 都已經(jīng)完成了刷盤,則提交事務(wù)。對應(yīng)時刻 B 崩潰恢復(fù)的情況。
????????可以看到,對于處于 prepare 階段的 redo log,即可以提交事務(wù),也可以回滾事務(wù),這取決于是否能在 binlog 中查找到與 redo log 相同的 XID,如果有就提交事務(wù),如果沒有就回滾事務(wù)。這樣就可以保證 redo log 和 binlog 這兩份日志的一致性了。
所以說,兩階段提交是以 binlog 寫成功為事務(wù)提交成功的標(biāo)識,因為 binlog 寫成功了,就意味著能在 binlog 中查找到與 redo log 相同的 XID。
事務(wù)沒提交的時候,redo log 會被持久化到磁盤嗎?
會的。事務(wù)執(zhí)行中間過程的 redo log 也是直接寫在 redo log buffer 中的,這些緩存在 redo log buffer 里的 redo log 也會被「后臺線程」每隔一秒一起持久化到磁盤。
修改操作流程
三個日志講完了,至此我們可以先小結(jié)下,update 語句的執(zhí)行過程。
當(dāng)優(yōu)化器分析出成本最小的執(zhí)行計劃后,執(zhí)行器就按照執(zhí)行計劃開始進(jìn)行更新操作。
具體更新一條記錄 UPDATE t_user SET name = 'xiaolin' WHERE id = 1;
的流程如下:
-
執(zhí)行器負(fù)責(zé)具體執(zhí)行,會調(diào)用存儲引擎的接口,通過主鍵索引樹搜索獲取 id = 1 這一行記錄:
-
如果 id=1 這一行所在的數(shù)據(jù)頁本來就在 buffer pool 中,就直接返回給執(zhí)行器更新;
-
如果記錄不在 buffer pool,將數(shù)據(jù)頁從磁盤讀入到 buffer pool,返回記錄給執(zhí)行器。
-
-
執(zhí)行器得到聚簇索引記錄后,會看一下更新前的記錄和更新后的記錄是否一樣:
-
如果一樣的話就不進(jìn)行后續(xù)更新流程;
-
如果不一樣的話就把更新前的記錄和更新后的記錄都當(dāng)作參數(shù)傳給 InnoDB 層,讓 InnoDB 真正的執(zhí)行更新記錄的操作;
-
-
開啟事務(wù), InnoDB 層更新記錄前,首先要記錄相應(yīng)的 undo log,因為這是更新操作,需要把被更新的列的舊值記下來,也就是要生成一條 undo log,undo log 會寫入 Buffer Pool 中的 Undo 頁面,不過在內(nèi)存修改該 Undo 頁面后,需要記錄對應(yīng)的 redo log。
-
InnoDB 層開始更新記錄,會先更新內(nèi)存(同時標(biāo)記為臟頁),然后將記錄寫到 redo log 里面,這個時候更新就算完成了。為了減少磁盤I/O,不會立即將臟頁寫入磁盤,后續(xù)由后臺線程選擇一個合適的時機將臟頁寫入到磁盤。這就是 WAL 技術(shù),MySQL 的寫操作并不是立刻寫到磁盤上,而是先寫 redo 日志,然后在合適的時間再將修改的行數(shù)據(jù)寫到磁盤上。
-
至此,一條記錄更新完了。
-
在一條更新語句執(zhí)行完成后,然后開始記錄該語句對應(yīng)的 binlog,此時記錄的 binlog 會被保存到 binlog cache,并沒有刷新到硬盤上的 binlog 文件,在事務(wù)提交時才會統(tǒng)一將該事務(wù)運行過程中的所有 binlog 刷新到硬盤。
-
事務(wù)提交,剩下的就是「兩階段提交」的事情了