
本文共:3018字
預計閱讀時間:8分鐘
文章首發于我的微信公衆号:哪兒來的moon,歡迎大家關注
mysql┃一條更新語句是怎麼執行的???
前言通過上一篇文章的内容,大家已經對mysql的基本架構有了了解,我也和大家簡單的聊了下一條查詢語句是怎麼在mysql中運作的。
今天moon就和大家聊聊一條更新語句在mysql中是怎麼走完全程的?
讀完今天的文章,你将會學到:
♠♥一條更新語句是怎麼執行的?
♠♥redolog,binlog日志子產品是做什麼的?都有哪些作用?
正文
moon上一篇文章和大家聊了,一條查詢語句執行過程當中要經過的子產品,有連接配接器,查詢緩存,分析器,優化器,執行器,存儲引擎。
那麼一條更新語句走完一整個流程又要經過哪些子產品呢?
假設一張表裡隻有一個主鍵id和一個int字段c
我們從如下代碼開始
update T set c=c+1 where ID=2;
其實查詢語句走的一套流程,基本上更新語句也會走一遍,但是更新語句還會涉及到另外兩個重要的日志子產品,redolog,binlog。
redolog(重做日志)首先要告訴大家,redolog是innodb獨有的,關于它具體做了什麼,moon先和大家舉個例子,不知道大家有沒有了解過從前街邊賣小吃老闆的記賬方式。
每來一個人,買一份煎餅,老闆都會在自己的賬單上記住+15元,假如今天第一份收入15元
+15 總金額 15
然後第二份
+15 總金額30
第三份
+15 總金額45
這樣看确實沒毛病,記得也很清楚,但是老闆生意越來越好,而且做生意也越來越複雜,除了賣煎餅,還會賣其他的東西,價格也不同,物種琳琅滿目,并且人也越來越多,老闆就沒辦法實時去計算了,于是老闆改了一種方式,分了兩個賬單。
第一個賬單每天隻記錄一次總的資訊(賬本)
2020年5月5号 收入5000 支出 1000 淨收入 4000
第二個賬單記錄每天的細節資訊(賬單)
2020年5月5日
+15
+16
+48
-23
...........
這樣的話老闆在忙的時候就不需要管計算的問題,隻需要簡單的記錄下金額就好,省去了低效率的計算過程,留在生意不忙的空閑時間去做。
沒錯,redolog在mysql 的日志系統就是類似于這種賬單模式,它是先寫日志,再寫磁盤,也就是先記賬,等不忙的時候再去計算寫賬本。
具體來說,當有一條記錄需要更新的時候,InnoDB引擎就會先把記錄寫到redo log裡面,并更新記憶體,這個時候更新就算完成了(其實還沒有寫入磁盤)。同時,InnoDB引擎會在适當的時候(空閑時間),将這個操作記錄更新到磁盤裡面。如果今天賬單的記錄不多,掌櫃可以等打烊後再整理。但如果某天賬單的特别多,賬本寫滿了,又怎麼辦呢?這個時候掌櫃隻好放下手中的活兒,把賬單中的一部分賒賬記錄更新到賬本中,然後把這些記錄從賬單上擦掉。與此類似,InnoDB的redo log是固定大小的,比如可以配置為一組4個檔案,每個檔案的大小是1GB,那麼這個“賬單”總共就可以記錄4GB的操作。從頭開始寫,寫到末尾就又回到開頭循環寫,如下面這個圖所示。
記錄點就是記錄你要執行的語句是什麼,寫點就是執行記錄點記錄的語句,當記錄點追上了寫點就會發生"
記憶體抖動",從表面上看就是mysql當機了一會兒,其實是innodb在執行redolog中記錄的内容。
有了redo log,InnoDB就可以保證即使資料庫發生異常重新開機,之前送出的記錄都不會丢失,這個能力稱為
crash-safe。
binlog(歸檔日志)我們剛剛說了redolog是innodb獨有的,那麼我們之前也講了,mysql其實是分為兩塊的,一塊兒是server層,另一塊兒才是存儲引擎層,那麼server層的日志是什麼?其實就是我們接下來要講的binlog。
其實在最早是隻有binlog的,因為在遠古時代mysql的存儲引擎隻有myisam,redolog是在後期innodb出現的時候也跟着一起來的,這兩個日志也是有很大差別的。
♠♥redo log是InnoDB引擎特有的;binlog是MySQL的Server層實作的,所有引擎都可以使用。
♠♥redo log是實體日志,記錄的是“在某個資料頁上做了什麼修改”;binlog是邏輯日志,記錄的是這個語句的原始邏輯,比如“給ID=2這一行的c字段加1 ”。
♠♥redo log是循環寫的,空間固定會用完;binlog是可以追加寫入的。“追加寫”是指binlog檔案寫到一定大小後會切換到下一個,并不會覆寫以前的日志。
我們來看看這條更新語句的執行流程
update T set c=c+1 where ID=2;
⑴.執行器先找引擎取ID=2這一行。ID是主鍵,引擎直接用樹搜尋找到這一行。
如果ID=2這一行所在的資料頁本來就在記憶體中,就直接傳回給執行器;
否則,需要先從磁盤讀入記憶體,然後再傳回。
⑵.執行器拿到引擎給的行資料,把這個值加上1,比如原來是N,現在就是N+1,得到新的一行資料,再調用引擎接口寫入這行新資料。
⑶.引擎将這行新資料更新到記憶體中,同時将這個更新操作記錄到redo log裡面,此時redo log處于prepare狀态。然後告知執行器執行完成了,随時可以送出事務。
⑷.執行器生成這個操作的binlog,并把binlog寫入磁盤。
⑸.執行器調用引擎的送出事務接口,引擎把剛剛寫入的redo log改成送出(commit)狀态,更新完成。
細心的已經發現上圖是先寫redolog,準備階段,之後再寫binlog,送出事務,commit階段。
為什麼會有兩階段送出呢?
我們先來看看,如果不适用兩階段送出會有什麼問題呢?
♠♥先寫redo log後寫binlog。假設在redo log寫完,binlog還沒有寫完的時候,MySQL程序異常重新開機。由于我們前面說過的,redo log寫完之後,系統即使崩潰,仍然能夠把資料恢複回來,是以恢複後這一行c的值是1。
但是由于binlog沒寫完就crash了,這時候binlog裡面就沒有記錄這個語句。是以,之後備份日志的時候,存起來的binlog裡面就沒有這條語句。
然後你會發現,如果需要用這個binlog來恢複臨時庫的話,由于這個語句的binlog丢失,這個臨時庫就會少了這一次更新,恢複出來的這一行c的值就是0,與原庫的值不同。
♠♥先寫binlog後寫redo log。如果在binlog寫完之後crash,由于redo log還沒寫,崩潰恢複以後這個事務無效,是以這一行c的值是0。但是binlog裡面已經記錄了“把c從0改成1”這個日志。是以,在之後用binlog來恢複的時候就多了一個事務出來,恢複出來的這一行c的值就是1,與原庫的值不同。
可以看到,如果不使用“兩階段送出”,那麼資料庫的狀态就有可能和用它的日志恢複出來的庫的狀态不一緻。
結語
今天moon和大家聊了一下mysql中最重要的兩個日志子產品,innodb專屬的redolog還有server層的binlog,相信大家也對一條更新語句的執行流程有了了解,當然這隻是概述,我會在後期講的越來越細。
下期就和大家了解了解mysql的索引模型,我們下期見~
借鑒:丁奇《mysql45講》