深入瞭解mysql中更新語句是如何工作的?

上一篇文章我詳解介紹了mysql中的查詢語句是如何工作的,我相信你還記得,一條查詢語句需要經過 連接器、分析器、優化器、執行器,最終才能到達存儲引擎。

那麼,更新語句是如何工作的呢?

我們來看一下下面這條語句

update T set money = money + 1 where ID = 1;

首先可以確認的是,查詢語句的那一套流程,更新語句也是同樣會走一遍。

執行更新語句首先通過連接器連接數據庫。

然後清空這個表的所有緩存。

接下來分析器進行詞法語法校驗,來識別這個語句是update語句。

然後優化器會選擇ID這個索引。

最後執行器負責執行。

目前看來,更新語句和查詢語句的執行好像沒有什麼區別。當然不是,在更新流程中,涉及到了兩個重要的日誌模塊。這也是更新區別於查詢的兩個重要操作。他們是redo log(重做日誌)和 binlog(歸檔日誌)。

redo log(重做日誌)InnoDB特有

mysql中每一次的更新操作都會和磁盤打交道,在磁盤中找到對應的記錄進行更新,這個IO成本是很高的。爲了解決這個問題,mysql使用了WAL 技術,WAL 的全稱是 Write-Ahead Logging,它的關鍵點就是先寫日誌,再寫磁盤。

具體來說,有一條記錄需要更新,InnoDB 引擎就會先把記錄先寫到 redo log中,並更新內存,這個時候更新就算完成了。這裏可能會有人不明白,還沒有更新磁盤,爲什麼說更新完成了呢?大家有一點需要明白,mysql的查詢是將磁盤中的信息加載到內存中,然後再返回,換言之讀取的就是內存。接下來,InnoDB 引擎會在適當的時候,將這個操作記錄更新到磁盤裏面,而這個更新往往是在系統比較空閒的時候做。

這時候又會產生一個問題,如果說系統一直比較忙的話,難道要一直向redo log中寫日誌嗎?這是肯定不行的,redo log是有固定大小的。比如可以配置爲一組 4 個文件,每個文件的大小是 1GB,那麼redo log總共就可以記錄 4GB 的操作。從頭開始寫,寫到末尾就又回到開頭循環寫,

有了redo log,InnoDB 就可以保證即使數據庫發生異常重啓,之前提交的記錄都不會丟失,這個能力稱爲 crash-safe。

延伸一下,如果你的系統寫入數據量很大的話,同時對於數據的查詢不需要很及時的話,可以採用類似的思想進行操作。比如數據可以先寫入到kafka中,然後由kafka寫入到mysql中。

 

binlog(歸檔日誌)

binlog(歸檔日誌)是server層的日誌,所有的引擎都可以使用。redo log 是物理日誌,記錄的是“在某個數據頁上做了什麼修改”;binlog 是邏輯日誌,記錄的是這個語句的原始邏輯,比如“給 ID=1 這一行的 money 字段加 1 ”。binlog 可以追加寫入數據,不會覆蓋之前的數據。

對這兩個日誌有了理解後,我們就可以回到開始時提的幾個問題了。

對於更新語句的執行流程

1. 執行器通過引擎找到id=1的這行數據。如果這條記錄已經在內存中了,就直接返回給執行器,否則就由磁盤讀取到內存中,然後再返回。

2. 執行器拿到返回的數據,進行加1操作,得到新的一行數據,然後調用引擎的接口,將數據更新到內存中,同時寫入到redo log中。此時的redo log處於prepare狀態,然後告知執行器執行完成了,隨時可以提交事務。

3. 執行器生成這個操作的 binlog,並把 binlog 寫入磁盤。

4. 執行器調用引擎的提交事務接口,引擎把剛剛寫入的 redo log 改成提交(commit)狀態,更新完成。

如上圖所示,我們發現redo log的寫入拆成了兩個步驟:prepare 和 commit,這就是"兩階段提交"。

如果你有分佈式開發經驗的話,應該很容易理解兩階段提交。在這裏主要是爲了讓兩份日誌的邏輯保持一致,以便出現問題進行恢復的時候數據一致。因爲這兩份日誌是不同邏輯中的,相當於分佈式事務的問題。

小結

mysql中的更新語句和查詢最大的區別在於很重要的兩個日誌,物理日誌 redo log 和邏輯日誌 binlog。換言之,你可以想象到,使用這兩個日誌是否能進行數據的恢復呢!

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