溫馨提示×

溫馨提示×

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

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

用一條SQL插入跟更新執(zhí)行流程以及日志系統(tǒng)原理

發(fā)布時間:2021-09-16 15:04:20 來源:億速云 閱讀:136 作者:chen 欄目:大數(shù)據(jù)

這篇文章主要介紹“用一條SQL插入跟更新執(zhí)行流程以及日志系統(tǒng)原理”,在日常操作中,相信很多人在用一條SQL插入跟更新執(zhí)行流程以及日志系統(tǒng)原理問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”用一條SQL插入跟更新執(zhí)行流程以及日志系統(tǒng)原理”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

用一條SQL插入跟更新執(zhí)行流程以及日志系統(tǒng)原理

查詢?nèi)绾螆?zhí)行

平時我們使用數(shù)據(jù)庫,看到的通常都是一個整體。比如,你有個最簡單的表,表里只有一個ID字段,在執(zhí)行下面這個查詢語句時:

mysql> select * from T where ID=10;

我們看到的只是輸入一條語句,返回一個結(jié)果,卻不知道這條語句在MySQL內(nèi)部的執(zhí)行過程。如何把這個過程拆解下來看后臺部分呢?下面我給出的是MySQL的基本架構(gòu)示意圖,從中你可以清楚地看到SQL語句在MySQL的各個功能模塊中的執(zhí)行過程。
用一條SQL插入跟更新執(zhí)行流程以及日志系統(tǒng)原理
大體來說,MySQL可以分為Server層存儲引擎層兩部分。

Server層:包括連接器、查詢緩存分析器、優(yōu)化器、執(zhí)行器等,涵蓋MySQL的大多數(shù)核心服務(wù)功能,以及所有的內(nèi)置函數(shù)(如日期、時間、數(shù)學(xué)和加密函數(shù)等),所有跨存儲引擎的功能都在這一層實現(xiàn),比如存儲過程、觸發(fā)器視圖等。

存儲引擎層:負(fù)責(zé)數(shù)據(jù)的存儲和提取。其架構(gòu)模式是插件式的,支持InnoDB、MyISAM、Memory等多個存儲引擎。現(xiàn)在最常用的存儲引擎是InnoDB,它從MySQL 5.5.5版本開始成為了默認(rèn)存儲引擎。

也就是說,你執(zhí)行create table建表的時候,如果不指定引擎類型,默認(rèn)使用的就是InnoDB。不過,你也可以通過指定存儲引擎的類型來選擇別的引擎,比如在create table語句中使用engine=memory, 來指定使用內(nèi)存引擎創(chuàng)建表。不同存儲引擎的表數(shù)據(jù)存取方式不同,支持的功能也不同。

從圖中不難看出,不同的存儲引擎共用一個Server層,也就是從連接器到執(zhí)行器的部分。你可以先對每個組件的名字有個印象,接下來我會結(jié)合開頭提到的那條SQL語句,帶你走一遍整個執(zhí)行流程,依次看下每個組件的作用。

連接器

第一步,你會先連接到這個數(shù)據(jù)庫上,這時候接待你的就是連接器。連接器負(fù)責(zé)跟客戶端建立連接、獲取權(quán)限、維持和管理連接。連接命令一般是這么寫的:

mysql -h 127.0.0.1 -P 3306 -u root -p
root

輸完命令之后,你就需要在交互對話里面輸入密碼。雖然密碼也可以直接跟在-p后面寫在命令行中,但這樣可能會導(dǎo)致你的密碼泄露。如果你連的是生產(chǎn)服務(wù)器,強(qiáng)烈建議你不要這么做。

連接命令中的mysql是客戶端工具,用來跟服務(wù)端建立連接。在完成經(jīng)典的TCP握手后,連接器就要開始認(rèn)證你的身份,這個時候用的就是你輸入的用戶名和密碼。

  • 如果用戶名或密碼不對,你就會收到一個Access denied for user的錯誤,然后客戶端程序結(jié)束執(zhí)行。

  • 如果用戶名密碼認(rèn)證通過,連接器會到權(quán)限表里面查出你擁有的權(quán)限。之后,這個連接里面的權(quán)限判斷邏輯,都將依賴于此時讀到的權(quán)限。

這就意味著,一個用戶成功建立連接后,即使你用管理員賬號對這個用戶的權(quán)限做了修改,也不會影響已經(jīng)存在連接的權(quán)限。修改完成后,只有再新建的連接才會使用新的權(quán)限設(shè)置。

連接完成后,如果你沒有后續(xù)的動作,這個連接就處于空閑狀態(tài),你可以在show processlist命令中看到它。文本中這個圖是show processlist的結(jié)果,其中的Command列顯示為Sleep的這一行,就表示現(xiàn)在系統(tǒng)里面有一個空閑連接。
用一條SQL插入跟更新執(zhí)行流程以及日志系統(tǒng)原理

客戶端如果太長時間沒動靜,連接器就會自動將它斷開。這個時間是由參數(shù)wait_timeout控制的,默認(rèn)值是8小時。

如果在連接被斷開之后,客戶端再次發(fā)送請求的話,就會收到一個錯誤提醒: Lost connection to MySQL server during query。這時候如果你要繼續(xù),就需要重連,然后再執(zhí)行請求了。

數(shù)據(jù)庫里面,長連接是指連接成功后,如果客戶端持續(xù)有請求,則一直使用同一個連接。短連接則是指每次執(zhí)行完很少的幾次查詢就斷開連接,下次查詢再重新建立一個。

建立連接的過程通常是比較復(fù)雜的,所以我建議你在使用中要盡量減少建立連接的動作,也就是盡量使用長連接。

但是全部使用長連接后,你可能會發(fā)現(xiàn),有些時候MySQL占用內(nèi)存漲得特別快,這是因為MySQL在執(zhí)行過程中臨時使用的內(nèi)存是管理在連接對象里面的。這些資源會在連接斷開的時候才釋放。所以如果長連接累積下來,可能導(dǎo)致內(nèi)存占用太大,被系統(tǒng)強(qiáng)行殺掉(OOM),從現(xiàn)象看就是MySQL異常重啟了。

怎么解決這個問題呢?你可以考慮以下兩種方案。

  1. 定期斷開長連接。使用一段時間,或者程序里面判斷執(zhí)行過一個占用內(nèi)存的大查詢后,斷開連接,之后要查詢再重連。

  2. 如果你用的是MySQL 5.7或更新版本,可以在每次執(zhí)行一個比較大的操作后,通過執(zhí)行 mysql_reset_connection來重新初始化連接資源。這個過程不需要重連和重新做權(quán)限驗證,但是會將連接恢復(fù)到剛剛創(chuàng)建完時的狀態(tài)。

查詢緩存

連接建立完成后,你就可以執(zhí)行select語句了。執(zhí)行邏輯就會來到第二步:查詢緩存。

MySQL拿到一個查詢請求后,會先到查詢緩存看看,之前是不是執(zhí)行過這條語句。之前執(zhí)行過的語句及其結(jié)果可能會以key-value對的形式,被直接緩存在內(nèi)存中。key是查詢的語句,value是查詢的結(jié)果。如果你的查詢能夠直接在這個緩存中找到key,那么這個value就會被直接返回給客戶端。

如果語句不在查詢緩存中,就會繼續(xù)后面的執(zhí)行階段。執(zhí)行完成后,執(zhí)行結(jié)果會被存入查詢緩存中。你可以看到,如果查詢命中緩存,MySQL不需要執(zhí)行后面的復(fù)雜操作,就可以直接返回結(jié)果,這個效率會很高。

但是大多數(shù)情況下我會建議你不要使用查詢緩存,為什么呢?因為查詢緩存往往弊大于利。

查詢緩存的失效非常頻繁,只要有對一個表的更新,這個表上所有的查詢緩存都會被清空。因此很可能你費勁地把結(jié)果存起來,還沒使用呢,就被一個更新全清空了。對于更新壓力大的數(shù)據(jù)庫來說,查詢緩存的命中率會非常低。除非你的業(yè)務(wù)就是有一張靜態(tài)表,很長時間才會更新一次。比如,一個系統(tǒng)配置表,那這張表上的查詢才適合使用查詢緩存。

好在MySQL也提供了這種按需使用的方式。你可以將參數(shù)query_cache_type設(shè)置成DEMAND,這樣對于默認(rèn)的SQL語句都不使用查詢緩存。而對于你確定要使用查詢緩存的語句,可以用SQL_CACHE顯式指定,像下面這個語句一樣:

mysql> select SQL_CACHE * from T where ID=10;

PS : MySQL 8.0版本直接將查詢緩存的整塊功能刪掉了,也就是說8.0開始徹底沒有這個功能了。

分析器

如果沒有命中查詢緩存,就要開始真正執(zhí)行語句了。首先,MySQL需要知道你要做什么,因此需要對SQL語句做解析。

分析器先會做詞法分析。你輸入的是由多個字符串和空格組成的一條SQL語句,MySQL需要識別出里面的字符串分別是什么,代表什么。

MySQL從你輸入的select這個關(guān)鍵字識別出來,這是一個查詢語句。它也要把字符串“T”識別成“表名T”,把字符串“ID”識別成“列ID”。

做完了這些識別以后,就要做“語法分析”。根據(jù)詞法分析的結(jié)果,語法分析器會根據(jù)語法規(guī)則,判斷你輸入的這個SQL語句是否滿足MySQL語法。

如果你的語句不對,就會收到You have an error in your SQL syntax的錯誤提醒,比如下面這個語句select少打了開頭的字母“s”。

mysql> elect * from t where ID=1;ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that correspondsto your MySQL server version for the right syntax to use near 'elect * from t where ID=1' at line 1

一般語法錯誤會提示第一個出現(xiàn)錯誤的位置,所以你要關(guān)注的是緊接use near的內(nèi)容。

優(yōu)化器

經(jīng)過了分析器,MySQL就知道你要做什么了。在開始執(zhí)行之前,還要先經(jīng)過優(yōu)化器的處理。

優(yōu)化器是在表里面有多個索引的時候,決定使用哪個索引;或者在一個語句有多表關(guān)聯(lián)(join)的時候,決定各個表的連接順序。比如你執(zhí)行下面這樣的語句,這個語句是執(zhí)行兩個表的join:

mysql> select * from t1 join t2 using(ID)  where t1.c=10 and t2.d=20;
  • 既可以先從表t1里面取出c=10的記錄的ID值,再根據(jù)ID值關(guān)聯(lián)到表t2,再判斷t2里面d的值是否等于20。

  • 也可以先從表t2里面取出d=20的記錄的ID值,再根據(jù)ID值關(guān)聯(lián)到t1,再判斷t1里面c的值是否等于10。

這兩種執(zhí)行方法的邏輯結(jié)果是一樣的,但是執(zhí)行的效率會有不同,而優(yōu)化器的作用就是決定選擇使用哪一個方案

優(yōu)化器階段完成后,這個語句的執(zhí)行方案就確定下來了,然后進(jìn)入執(zhí)行器階段。如果你還有一些疑問,比如優(yōu)化器是怎么選擇索引的,有沒有可能選擇錯等等,沒關(guān)系,我會在后面的文章中單獨展開說明優(yōu)化器的內(nèi)容。

執(zhí)行器

MySQL通過分析器知道了你要做什么,通過優(yōu)化器知道了該怎么做,于是就進(jìn)入了執(zhí)行器階段,開始執(zhí)行語句。

開始執(zhí)行的時候,要先判斷一下你對這個表T有沒有執(zhí)行查詢的權(quán)限,如果沒有,就會返回沒有權(quán)限的錯誤,如下所示(在工程實現(xiàn)上,如果命中查詢緩存,會在查詢緩存放回結(jié)果的時候,做權(quán)限驗證。查詢也會在優(yōu)化器之前調(diào)用precheck驗證權(quán)限)。

mysql> select * from T where ID=10;ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'

如果有權(quán)限,就打開表繼續(xù)執(zhí)行。打開表的時候,執(zhí)行器就會根據(jù)表的引擎定義,去使用這個引擎提供的接口。

比如我們這個例子中的表T中,ID字段沒有索引,那么執(zhí)行器的執(zhí)行流程是這樣的:

  1. 調(diào)用InnoDB引擎接口取這個表的第一行,判斷ID值是不是10,如果不是則跳過,如果是則將這行存在結(jié)果集中;

  2. 調(diào)用引擎接口取下一行,重復(fù)相同的判斷邏輯,直到取到這個表的最后一行。

  3. 執(zhí)行器將上述遍歷過程中所有滿足條件的行組成的記錄集作為結(jié)果集返回給客戶端。

至此,這個語句就執(zhí)行完成了。

對于有索引的表,執(zhí)行的邏輯也差不多。第一次調(diào)用的是取滿足條件的第一行這個接口,之后循環(huán)取滿足條件的下一行這個接口,這些接口都是引擎中已經(jīng)定義好的。

你會在數(shù)據(jù)庫的慢查詢?nèi)罩局锌吹揭粋€rows_examined的字段,表示這個語句執(zhí)行過程中掃描了多少行。這個值就是在執(zhí)行器每次調(diào)用引擎獲取數(shù)據(jù)行的時候累加的。

在有些場景下,執(zhí)行器調(diào)用一次,在引擎內(nèi)部則掃描了多行,因此引擎掃描行數(shù)跟rows_examined并不是完全相同的。

PS:如果表T中沒有字段k,而你執(zhí)行了這個語句 select * from T where k=1, 那肯定是會報“不存在這個列”的錯誤: “Unknown column ‘k’ in ‘where clause’”。該錯誤會在分析器爆出。

總結(jié):

一條查詢語句的執(zhí)行過程一般是經(jīng)過連接器、分析器、優(yōu)化器、執(zhí)行器等功能模塊,最后到達(dá)存儲引擎。

更新如何執(zhí)行

一條更新語句的執(zhí)行流程又是怎樣的呢?之前你可能經(jīng)常聽DBA同事說,MySQL可以恢復(fù)到半個月內(nèi)任意一秒的狀態(tài),驚嘆的同時,你是不是心中也會不免會好奇,這是怎樣做到的呢?

我們還是從一個表的一條更新語句說起,下面是這個表的創(chuàng)建語句,這個表有一個主鍵ID和一個整型字段c:

mysql> create table T(ID int primary key, c int);

如果要將ID=2這一行的值加1,SQL語句就會這么寫:

mysql> update T set c=c+1 where ID=2;

前面我有跟你介紹過SQL語句基本的執(zhí)行鏈路,這里我再把那張圖拿過來,你也可以先簡單看看這個圖回顧下。首先,可以確定的說,查詢語句的那一套流程,更新語句也是同樣會走一遍。
用一條SQL插入跟更新執(zhí)行流程以及日志系統(tǒng)原理
你執(zhí)行語句前要先連接數(shù)據(jù)庫,這是連接器的工作。

前面我們說過,在一個表上有更新的時候,跟這個表有關(guān)的查詢緩存會失效,所以這條語句就會把表T上所有緩存結(jié)果都清空。這也就是我們一般不建議使用查詢緩存的原因。

接下來,分析器會通過詞法和語法解析知道這是一條更新語句。優(yōu)化器決定要使用ID這個索引。然后,執(zhí)行器負(fù)責(zé)具體執(zhí)行,找到這一行,然后更新。

與查詢流程不一樣的是,更新流程還涉及兩個重要的日志模塊,它們正是我們今天要討論的主角:redo log(重做日志)和 binlog(歸檔日志)。binlog內(nèi)容查看,如果接觸MySQL,那這兩個詞肯定是繞不過的,我后面的內(nèi)容里也會不斷地和你強(qiáng)調(diào)。不過話說回來,redo log和binlog在設(shè)計上有很多有意思的地方,這些設(shè)計思路也可以用到你自己的程序里。

重要的日志模塊:redo log

不知道你還記不記得《孔乙己》這篇文章,酒店掌柜有一個粉板,專門用來記錄客人的賒賬記錄。如果賒賬的人不多,那么他可以把顧客名和賬目寫在板上。但如果賒賬的人多了,粉板總會有記不下的時候,這個時候掌柜一定還有一個專門記錄賒賬的賬本

如果有人要賒賬或者還賬的話,掌柜一般有兩種做法:

  • 一種做法是直接把賬本翻出來,把這次賒的賬加上去或者扣除掉;

  • 另一種做法是先在粉板上記下這次的賬,等打烊以后再把賬本翻出來核算。

在生意紅火柜臺很忙時,掌柜一定會選擇后者,因為前者操作實在是太麻煩了。首先,你得找到這個人的賒賬總額那條記錄。你想想,密密麻麻幾十頁,掌柜要找到那個名字,可能還得帶上老花鏡慢慢找,找到之后再拿出算盤計算,最后再將結(jié)果寫回到賬本上。

這整個過程想想都麻煩。相比之下,還是先在粉板上記一下方便。你想想,如果掌柜沒有粉板的幫助,每次記賬都得翻賬本,效率是不是低得讓人難以忍受?

同樣,在MySQL里也有這個問題,如果每一次的更新操作都需要寫進(jìn)磁盤,然后磁盤也要找到對應(yīng)的那條記錄,然后再更新,整個過程IO成本、查找成本都很高。為了解決這個問題,MySQL的設(shè)計者就用了類似酒店掌柜粉板的思路來提升更新效率。

而粉板和賬本配合的整個過程,其實就是MySQL里經(jīng)常說到的WAL技術(shù),WAL的全稱是Write-Ahead Logging,它的關(guān)鍵點就是先寫日志,再寫磁盤,也就是先寫粉板,等不忙的時候再寫賬本。

具體來說,當(dāng)有一條記錄需要更新的時候,InnoDB引擎就會先把記錄寫到redo log(粉板)里面,并更新內(nèi)存,這個時候更新就算完成了。同時,InnoDB引擎會在適當(dāng)?shù)臅r候,將這個操作記錄更新到磁盤里面,而這個更新往往是在系統(tǒng)比較空閑的時候做,這就像打烊以后掌柜做的事。

如果今天賒賬的不多,掌柜可以等打烊后再整理。但如果某天賒賬的特別多,粉板寫滿了,又怎么辦呢?這個時候掌柜只好放下手中的活兒,把粉板中的一部分賒賬記錄更新到賬本中,然后把這些記錄從粉板上擦掉,為記新賬騰出空間。

與此類似,InnoDB的redo log是固定大小的,比如可以配置為一組4個文件,每個文件的大小是1GB,那么這塊粉板總共就可以記錄4GB的操作。從頭開始寫,寫到末尾就又回到開頭循環(huán)寫,如下面這個圖所示。
用一條SQL插入跟更新執(zhí)行流程以及日志系統(tǒng)原理
write pos :是當(dāng)前記錄的位置,一邊寫一邊后移,寫到第3號文件末尾后就回到0號文件開頭。
checkpoint:是當(dāng)前要擦除的位置,也是往后推移并且循環(huán)的,擦除記錄前要把記錄更新到數(shù)據(jù)文件。

write pos和checkpoint之間的是粉板上還空著的部分,可以用來記錄新的操作。如果write pos追上checkpoint,表示粉板滿了,這時候不能再執(zhí)行新的更新,得停下來先擦掉一些記錄,把checkpoint推進(jìn)一下。

有了redo log,InnoDB就可以保證即使數(shù)據(jù)庫發(fā)生異常重啟,之前提交的記錄都不會丟失,這個能力稱為crash-safe。記錄這個頁 做了什么改動。

要理解crash-safe這個概念,可以想想我們前面賒賬記錄的例子。只要賒賬記錄記在了粉板上或?qū)懺诹速~本上,之后即使掌柜忘記了,比如突然停業(yè)幾天,恢復(fù)生意后依然可以通過賬本和粉板上的數(shù)據(jù)明確賒賬賬目。

重要的日志模塊:binlog

前面我們講過,MySQL整體來看,其實就有兩塊:一塊是Server層,它主要做的是MySQL功能層面的事情;還有一塊是引擎層,負(fù)責(zé)存儲相關(guān)的具體事宜。上面我們聊到的粉板redo log是InnoDB引擎特有的日志,而Server層也有自己的日志,稱為binlog(歸檔日志)。 Binlog有兩種模式,statement 格式的話是記sql語句, row格式會記錄行的內(nèi)容,記兩條,更新前和更新后都有。

我想你肯定會問,為什么會有兩份日志呢?
因為最開始MySQL里并沒有InnoDB引擎。MySQL自帶的引擎是MyISAM,但是MyISAM沒有crash-safe的能力,binlog日志只能用于歸檔。而InnoDB是另一個公司以插件形式引入MySQL的,既然只依靠binlog是沒有crash-safe能力的,所以InnoDB使用另外一套日志系統(tǒng)——也就是redo log來實現(xiàn)crash-safe能力。

這兩種日志有以下三點不同。

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

  2. redo log是物理日志,記錄的是在某個數(shù)據(jù)頁上做了什么修改;binlog是邏輯日志,記錄的是這個語句的原始邏輯,比如給ID=2這一行的c字段加1

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

有了對這兩個日志的概念性理解,我們再來看執(zhí)行器和InnoDB引擎在執(zhí)行這個簡單的update語句時的內(nèi)部流程。

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

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

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

  4. 執(zhí)行器生成這個操作的binlog,并把binlog寫入磁盤。

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

這里我給出這個update語句的執(zhí)行流程圖,圖中淺色框表示是在InnoDB內(nèi)部執(zhí)行的,深色框表示是在執(zhí)行器中執(zhí)行的。

用一條SQL插入跟更新執(zhí)行流程以及日志系統(tǒng)原理
你可能注意到了,最后三步看上去有點,將redo log的寫入拆成了兩個步驟:prepare和commit,這就是兩階段提交。

兩階段提交
為什么必須有兩階段提交呢?這是為了讓兩份日志之間的邏輯一致。要說明這個問題,我們得從文章開頭的那個問題說起:怎樣讓數(shù)據(jù)庫恢復(fù)到半個月內(nèi)任意一秒的狀態(tài)?

前面我們說過了,binlog會記錄所有的邏輯操作,并且是采用追加寫的形式。如果你的DBA承諾說半個月內(nèi)可以恢復(fù),那么備份系統(tǒng)中一定會保存最近半個月的所有binlog,同時系統(tǒng)會定期做整庫備份。這里的定期取決于系統(tǒng)的重要性,可以是一天一備,也可以是一周一備。

當(dāng)需要恢復(fù)到指定的某一秒時,比如某天下午兩點發(fā)現(xiàn)中午十二點有一次誤刪表,需要找回數(shù)據(jù),那你可以這么做:

  • 首先,找到最近的一次全量備份,如果你運氣好,可能就是昨天晚上的一個備份,從這個備份恢復(fù)到臨時庫;

  • 然后,從備份的時間點開始,將備份的binlog依次取出來,重放到中午誤刪表之前的那個時刻。

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

好了,說完了數(shù)據(jù)恢復(fù)過程,我們回來說說,為什么日志需要兩階段提交。這里不妨用反證法來進(jìn)行解釋。

由于redo log和binlog是兩個獨立的邏輯,如果不用兩階段提交,要么就是先寫完redo log再寫binlog,或者采用反過來的順序。我們看看這兩種方式會有什么問題。

仍然用前面的update語句來做例子。假設(shè)當(dāng)前ID=2的行,字段c的值是0,再假設(shè)執(zhí)行update語句過程中在寫完第一個日志后,第二個日志還沒有寫完期間發(fā)生了crash,會出現(xiàn)什么情況呢?

  1. 先寫redo log后寫binlog。假設(shè)在redo log寫完,binlog還沒有寫完的時候,MySQL進(jìn)程異常重啟。由于我們前面說過的,redo log寫完之后,系統(tǒng)即使崩潰,仍然能夠把數(shù)據(jù)恢復(fù)回來,所以恢復(fù)后這一行c的值是1。但是由于binlog沒寫完就crash了,這時候binlog里面就沒有記錄這個語句。因此,之后備份日志的時候,存起來的binlog里面就沒有這條語句。然后你會發(fā)現(xiàn),如果需要用這個binlog來恢復(fù)臨時庫的話,由于這個語句的binlog丟失,這個臨時庫就會少了這一次更新,恢復(fù)出來的這一行c的值就是0,與原庫的值不同。

  2. 先寫binlog后寫redo log。如果在binlog寫完之后crash,由于redo log還沒寫,崩潰恢復(fù)以后這個事務(wù)無效,所以這一行c的值是0。但是binlog里面已經(jīng)記錄了把c從0改成1這個日志。所以,在之后用binlog來恢復(fù)的時候就多了一個事務(wù)出來,恢復(fù)出來的這一行c的值就是1,與原庫的值不同。

可以看到,如果不使用兩階段提交,那么數(shù)據(jù)庫的狀態(tài)就有可能和用它的日志恢復(fù)出來的庫的狀態(tài)不一致。

1 prepare階段 --> 2 寫binlog --> 3 commit
當(dāng)在2之前崩潰時
重啟恢復(fù):后發(fā)現(xiàn)沒有commit,回滾。備份恢復(fù):沒有binlog 。一致
當(dāng)在3之前崩潰
重啟恢復(fù):雖沒有commit,但滿足prepare和binlog完整,所以重啟后會自動commit。備份:有binlog. 一致

你可能會說,這個概率是不是很低,平時也沒有什么動不動就需要恢復(fù)臨時庫的場景呀?

其實不是的,不只是誤操作后需要用這個過程來恢復(fù)數(shù)據(jù)。當(dāng)你需要擴(kuò)容的時候,也就是需要再多搭建一些備庫來增加系統(tǒng)的讀能力的時候,現(xiàn)在常見的做法也是用全量備份加上應(yīng)用binlog來實現(xiàn)的,這個不一致就會導(dǎo)致你的線上出現(xiàn)主從數(shù)據(jù)庫不一致的情況。

簡單說,redo log和binlog都可以用于表示事務(wù)的提交狀態(tài),而兩階段提交就是讓這兩個狀態(tài)保持邏輯上的一致。

總結(jié)

介紹了MySQL里面最重要的兩個日志,即物理日志redo log邏輯日志binlog。

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

binlog還不能去掉。一個原因是,redolog只有InnoDB有,別的引擎沒有。另一個原因是,redolog是循環(huán)寫的,不持久保存,binlog的歸檔這個功能,redolog是不具備的。

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

介紹了與MySQL日志系統(tǒng)密切相關(guān)的兩階段提交。兩階段提交是跨系統(tǒng)維持?jǐn)?shù)據(jù)邏輯一致性時常用的一個方案,即使你不做數(shù)據(jù)庫內(nèi)核開發(fā),日常開發(fā)中也有可能會用到。

一天一備跟一周一備的對比
在一天一備的模式里,最壞情況下需要應(yīng)用一天的binlog。比如,你每天0點做一次全量備份,而要恢復(fù)出一個到昨天晚上23點的備份。一周一備最壞情況就要應(yīng)用一周的binlog了。系統(tǒng)的對應(yīng)指標(biāo)就是 提到的RTO(恢復(fù)目標(biāo)時間)。當(dāng)然這個是有成本的,因為更頻繁全量備份需要消耗更多存儲空間,所以這個RTO是成本換來的,就需要你根據(jù)業(yè)務(wù)重要性來評估了。

到此,關(guān)于“用一條SQL插入跟更新執(zhí)行流程以及日志系統(tǒng)原理”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

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

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

sql
AI