一條sql更新語句是如何執行的?


之前經常聽DBA的同事說,mysql可以恢復到半個月內任意1秒的狀態,驚歎的同時,你是不是你不免有一些好奇,這是怎麼做到的呢?
我們還是從一張表的一條更新語句說起,下面是這個表的創建語句,這個表有一個主鍵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語句基本的執行鏈路.這裏更新語句同樣適用
在這裏插入圖片描述
你執行語句之前要先連接數據庫,這是連接器的工作。
前面我們說過,在一個表上有更新的時候,更這個表有關的查詢緩存會失效,所以這條語句就會把T表上所有的緩存結果清空,這也是我們一般不使用查詢緩存的原因。

接下來,分析器會通過詞法分析,語法分析解析知道這是一條更新沿語句,優化器決定要使用ID這個索引。然後執行器負責具體執行,找到這一行然後更新。

與查詢流程不一樣的是更新流程還涉及到兩個重要的日誌模塊,它們是今天要討論的主角 redo log(重做日誌)binlog(歸檔日誌)。如果接觸Mysql這兩個詞肯定是繞不過的。接下來分介紹這兩種日誌

重要的日誌模塊 :redo log

不知道你還記不記得《孔乙己》這篇文章,酒店掌櫃有一個粉板,專門用來記錄客人的賒賬記錄,如果賒賬的人不多,那麼t她可以把顧客名和賬目寫在粉板上。但如果賒賬的人多了,粉板總會有記不下的時候,這時候掌櫃一定還有一個專門記錄賒賬的賬本。
如果有人要賒賬或者還賬的時候,掌櫃一般有兩種做法:
一種做法是直接把賬本翻出來,把這個賒賬的記錄加上去或者刪除掉:
另一種做法是先在粉本上記下這次的賬,等打烊後再把賬本翻出來覈算。
在生意紅火櫃檯很忙時,掌櫃一定會選擇後者,因爲前者操作實在是太麻煩了。首先你得找到這個人賒賬的總額那條記錄。你想想看密密麻麻幾十頁,掌櫃要找到那個名字,還得帶上老花鏡慢慢找,找到之後再拿出來盤算計算,然後再將結果寫回到賬本上。
這個過程想想都麻煩。相比之下,還是先在粉本上記一下方便。你想想如果掌櫃沒有粉板的幫助,每次記賬都得翻賬本,效率是不是低的讓人難以忍受?
同樣在Mysql裏也有這個問題,如果每一次的更新操作都需要寫進磁盤,然後磁盤也要找到那條對應的記錄,然後再更新,整個過程IO成本,查找成本都很高。爲了解決這個問題, Mysql的設計者就用了類似酒店掌櫃粉板的思路來提升更新效率。
而粉板和賬本配合的整個過程,其實就是mysql裏常說WAL技術,WAL的全稱是Write-Ahead Loging 它的關鍵是先寫日誌 再寫磁盤 也就是先寫粉板 等不忙的時候再寫賬本
具體來說,當一條記錄需要更新的時候,InnoDB引擎就會先把記錄寫到redo log(粉板)裏面,並更新內存,這個時候更新就算是完成了。同時InnoDB 引擎會在適當的時候,將這個記錄更新到磁盤裏,而這個更新往往是在系統比較空閒的時候做,這就像打烊之後老闆做的事。
如果今天賒賬不多,掌櫃可以等打烊之後再整理,但如果某天賒賬特別多,粉板寫滿了又怎麼辦呢?和時候掌櫃只好放下手中的活,把粉板的一部分記錄更新到賬本中,然後把這些記錄從粉板上擦掉,爲記新賬騰出空間
與此類似,InnoDB的redo log是固定大小的,比如可以配置一組4個文件,每個文件打大小1GB,那麼這塊“粉板‘總共就可以記錄4GB的操作。從頭開始寫,寫到末尾又回到開頭循環寫 如圖所示:
在這裏插入圖片描述
write pos是記錄當前位置,一邊寫一邊往後移,寫到第三號文件的末尾就回到0號文件開頭。
checkpoint 是當前要擦除的位置,也是往後推移並且循環的,擦除記錄前要把記錄更新到數據文件
write pos和checkpoint之間的是“粉板”還空着的部分,可以用來記錄新的操作。如果write pos追上 checkpoint 表示粉板已經滿了,這個時候不能再執行新的更新,得停下來先擦掉一些記錄,把checkpint推進一下。
有了redo log ,innoDB就可以保證即使數據庫發生異常重啓,之前提交的記錄不會丟失,這個能力被稱爲 crash-sale
要理解 crash-safe這個概念,可以想想我們前面賒賬的例子。只要賒賬記錄在粉本上或者寫在了賬本上,之後即使掌櫃忘記了,比如突然停業幾天,恢復生意後依然可以通過賬本和粉板上的數據明確賒賬 賬目

重要日誌模塊 binlog

前面講過,Mysql整體來看其實就兩塊,一塊是Server層 它主要做的是mysql功能層面的事情;還有一塊是引擎層,負責存儲相關的具體事宜。上面我們聊到的粉板redo log是InnoDB引擎特有的日誌,而Server層也有自己的日誌,稱爲binlog(歸檔日誌)

我想你肯定會問會什麼會有兩種日誌呢?
因爲最開始Mysql裏面並沒有InnoDB引擎.Mysql自帶的引擎是Mylsam,但是Mylsam沒有crash-safe的能力,binlog日誌只用于歸檔。而InnoDB是另一個公司以插件的形式引入Mysql的既然只依靠binlog是沒有crash-safe能力的,所以innoDB使用另外一套日誌系統–也就是rodo log來實現crash-safe 能力
這兩種日誌有以下三點不同
1.redo log是innodb 引擎層特有的;binlog是Mysql的Server層實現的,所有引擎都可以使用。
2.redo log是物理日誌,記錄的是在“某個數據頁上做了什麼修改”;binlog是邏輯日誌,記錄的是這個語句的原始邏輯,比如給ID=2這一行的C字段+1
3.redo log是循環寫的,空間固定會用完;binlog是可以追加寫入的。“追加寫”是指binlog寫到一定大小就會切換下一個,並不會覆蓋以前的日誌。
有了對這兩個日誌概念性的理解 我們來看下執行器和InnoDB引擎在執行這個簡單的update語句時的內部流程
1.執行器先找到引擎取 ID=2 這一行。ID是主鍵,引擎直接用樹搜索到這一行。如果ID=2這一行所在的數據頁本來就在內存中,就直接返回給執行器;否則需要先從磁盤讀入內存,然後返回
2.執行器 拿到引擎給的行數據 把這個值加上1,比如原來是N,新的一行是N+1 得到新的一行的數據,在調用引擎接口寫入這行新數據。
3.引擎將這行新數據更新到內存中,同時將這個更新記錄操作記錄到redolog裏面。此時redo log處於prepare 狀態。然後告知執行器 執行完了,隨時可以提交事務。
4.執行器 生成這個操作的binlog 並把binlog寫入磁盤。
5。執行器 調用提交事務的接口,引擎把剛剛寫入的redo log提交(commit)狀態更新完成
綠色表示在執行器中執行 藍色表示在InnoDB存儲引擎中執行
在這裏插入圖片描述
你可能注意到,最後三步有點“繞”。將reloglog的寫入拆成了兩個步驟:prepare和commit 這就是“”兩階段提交

兩階段提交

爲什麼必須有“兩階段提交”呢?這就是爲了讓兩份日誌間的邏輯一致。要說明這個問題。我們得從?文章開頭的那個問題說起:怎麼樣讓數據恢復到半個月內任意一秒的狀態?
前面我們說過,binlog會記錄所有的邏輯操作,採用的是“追加寫的”形式,如果你的DBA承諾說半個月內可以恢復,那麼備份系統中一定會保存最近半個月的所有binlog ,同時系統會定期做整庫備份。這裏“定期”取決於定期的重要性。可以一天一備,也可以一天一備。
當需要恢復到指定的某一秒時,比如某天下午兩點發現中午十二點有一次誤刪表,需要找回數據,那你可以這麼做:
首先,找到最近一份的全量備份,如果你運氣好,可能就是昨晚的一個備份,從這個備份恢復到臨時庫
然後從備份時間點開始,將備份的binlog依次取出來,重放到中午誤刪表之前的那個時刻
這樣你的臨時庫就跟誤刪之前的線上庫一樣了,然後你可以把表數據從臨時庫取出來,按需要恢 復到線上庫去
好了,說完了數據恢復過程,我們回來說說,爲什麼日誌需要“兩階段提交”。這裏不妨用反證法 來進行解釋。
由於redo log和binlog是兩個獨立的邏輯,如果不用兩階段提交,要麼就是先寫完redo log再寫 binlog,或者採用反過來的順序。我們看看這兩種方式會有什麼問題。
仍然用前面的update語句來做例子。假設當前ID=2的行,字段c的值是0,再假設執行update語 句過程中在寫完第一個日誌後,第二個日誌還沒有寫完期間發生了crash,會出現什麼情況呢?

  1. 先先寫寫rreeddoo lloogg後後寫寫bbiinnlloogg。假設在redo log寫完,binlog還沒有寫完的時候,MySQL進程異 常重啓。由於我們前面說過的,redo log寫完之後,系統即使崩潰,仍然能夠把數據恢復回 來,所以恢復後這一行c的值是1。 但是由於binlog沒寫完就crash了,這時候binlog裏面就沒有記錄這個語句。因此,之後備份 日誌的時候,存起來的binlog裏面就沒有這條語句。 然後你會發現,如果需要用這個binlog來恢復臨時庫的話,由於這個語句的binlog丟失,這 個臨時庫就會少了這一次更新,恢復出來的這一行c的值就是0,與原庫的值不同。
  2. 先先寫寫bbiinnlloogg後後寫寫rreeddoo lloogg。如果在binlog寫完之後crash,由於redo log還沒寫,崩潰恢復以 後這個事務無效,所以這一行c的值是0。但是binlog裏面已經記錄了“把c從0改成1”這個日 志。所以,在之後用binlog來恢復的時候就多了一個事務出來,恢復出來的這一行c的值就是 1,與原庫的值不同。

可以看到,如果不使用“兩階段提交”,那麼數據庫的狀態就有可能和用它的日誌恢復出來的庫的 狀態不一致。 你可能會說,這個概率是不是很低,平時也沒有什麼動不動就需要恢復臨時庫的場景呀? 其實不是的,不只是誤操作後需要用這個過程來恢復數據。當你需要擴容的時候,也就是需要再 多搭建一些備庫來增加系統的讀能力的時候,現在常見的做法也是用全量備份加上應用binlog來 實現的,這個“不一致”就會導致你的線上出現主從數據庫不一致的情況。 簡單說,redo log和binlog都可以用於表示事務的提交狀態,而兩階段提交就是讓這兩個狀態保 持邏輯上的一致。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章