mysql學習筆記---從日誌系統分析一條sql更新語句的執行

1.sql查詢流程的流程圖

我們知道,一條普通的查詢語句的執行流程一般是這樣的:

這裏的查詢緩存是針對表的一個緩存,如果對錶有過更新操作,那麼查詢緩存會立即失效,因此mysql的查詢緩存一般情況下意義不大.

2.更新語句的執行方式

上面簡單介紹了mysql的查詢流程,那麼對於更新語句來說,例如
update t_student set name='paul' where id=3;
同樣也會經歷上面的一個過程,分析器通過語法分析發現這是一個更新語句,然後優化器決定走id這個主鍵索引,最後交給執行器去連接到存儲引擎去執行實際的更新操作.但是我們知道,mysql的數據是存在磁盤上的,它的存儲實際上是有個最小的頁爲單位的,每次讀取最小會讀取一頁的數據到內存裏,當我們進行修改時,實際上也是直接對內存裏的這行數據進行修改.如果每次修改了都要寫回到磁盤中,那還得經過幾次io操作定位到這行數據並寫入,對於一次普通的寫入操作來說這種效率是很低的.那麼有沒有什麼好辦法呢?我們來參考生活中的一個例子.

3.mysql的日誌系統

3.1 生活中的例子

假設我們開了一家小賣部,我們對每筆交易肯定要進行記賬.首先我們有個賬本,上面記錄了所有的賬目(實際的數據)這是一個很大的本子上,每次翻找定位起來效率比較低,那我們可以弄一個小黑板,先將記錄臨時記在這個小黑板上,同時我可以先將修改記在腦海中(內存)當黑板記滿了,或者生意比較好我記不住了或者我空閒下來的時候,再將記憶中的內容抄寫到賬本里,同時把黑板上的相同的修改內容除去,這樣是不是就減少了頻繁翻閱賬本的次數,從而提高了工作效率~~

3.2 redo log

上面例子中的小黑板,其實就是mysql中innodb特有的日誌模塊redo log.這種思想類似於io操作中緩衝區的概念.它長下面這個模樣~
在這裏插入圖片描述
是一個環形的ringBuffer,上面有兩個指針,一個write pos,這個指針用來寫,當有更新記錄寫入時往前走,另一個是check point,是擦除指針,當我們把內存中的數據刷到磁盤裏去的時候這個指針就往前走,如果write pos走得太快和check point重疊了,這個環形就滿了,就像記賬的小黑板滿了,那麼此時系統不得不暫停一下把redo log的數據刷到磁盤中去,否則就會丟失數據嘛.
這種配合方式稱作WAL(Write ahead logging),他的完整流程是:我在執行一條更新語句時,先寫入redo log,再更新內存中的頁,最後在合適的時候統一將redo log寫到磁盤中去,從而更新真正的底層記錄.這樣即使中途mysql宕機了,由於redo log已經記錄了修改,我之前執行的修改記錄也不會丟失.就像我們的小店,某天賬本突然丟了,由於我將之前的修改都存在了小黑板上,在找回賬本後這部分記錄仍然還是存在的.

3.3 binlog

binlog這個東西相信不少同學的聽說過.我們日常工作中,DBA有時候會吹逼,說數據庫可以恢復到最近一兩個月的任意時候數據,這裏其實就是運用了binlog.那麼這玩意跟我們上面介紹的redo log有啥區別呢,區別如下:

  1. redo log是innodb特有的機制,現在mysql的默認存儲引擎innodb是後面才引入的,之前還有mysiam等存儲引擎,而在存儲引擎之上的server層,mysql自己封裝了一套日誌系統就是binlog 也就是他適用於所有存儲引擎.
  2. redo log 是一個環形的數據結構,受配置大小限制,本身存儲的內容有限,例如想恢復到一個月前的數據,肯定滿足不了.而binlog本身是採用的一種追加的寫入方式,一個文件寫完了可以繼續寫下一個文件,能夠支持較大的容量.能支持完整時間較長的數據恢復.
  3. redo log記錄的是修改的內容,是redo log 是物理日誌,記錄的是“在某個數據頁上做了什麼修改”;而binlog 是邏輯日誌,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。

4.完整的執行流程和兩階段提交

4.1 執行流程再分析

有了上面的日誌系統,我們再來看下一條DML的實際執行過程.
在這裏插入圖片描述

1.執行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用聚集索引找到這一行。如果 ID=2 這一行所在的數據頁本來就在內存中,就直接返回給執行器;否則,需要先從磁盤讀入內存,然後再返回。
2.執行器拿到引擎給的行數據,把這個值加上 1,比如原來是 N,現在就是 N+1,得到新的一行數據,再調用引擎接口寫入這行新數據。
3.引擎將這行新數據更新到內存中,同時將這個更新操作記錄到 redo log 裏面,此時 redo log 處於 prepare 狀態。然後告知執行器執行完成了,隨時可以提交事務。
4.執行器生成這個操作的 binlog,並把 binlog 寫入磁盤。
5.執行器調用引擎的提交事務接口,引擎把剛剛寫入的 redo log 改成提交(commit)狀態,更新完成。

可以發現其實我們平時對mysql的更新操作只是在內存裏去做修改,然後寫日誌到磁盤中就完事了.那肯定會出現內存的數據跟硬盤中的數據不一致的情況吧,這種情況我們稱爲"髒頁",這塊知識盆友們可以去google下了解~
然後注意這裏的commit狀態,跟我們平時的mysql事務中的commit操作不同,可以理解爲前者是後者執行過程中的一箇中間狀態~

4.2 兩階段提交

眼尖的盆友發現了,我們上面的3.4步實際上是在一個事務中的,這裏的事務是指存儲引擎中的一個內部事務.他避免了兩種日誌的不一致狀態,我們可以想下,如果沒有這個事務保證,那麼就有可能出現不一致的狀態:
1.先寫redo log再寫binlog 假設我們寫完redo log後系統就奔潰了,那麼redo log和內存中實際上已經有這個事務了,實際上這條修改已經生效了,但是binlog裏又是沒有的,假如我們今後用這份binlog來恢復數據庫,實際上恢復出來的數據就是不一致的數據.
2.先寫binlog再寫redo log 假如我們先寫binlog,寫完之後數據庫crash了,由於redolog裏沒有這一條修改,實際上這個事務是無效的,系統恢復後是沒有這條修改的,假如我們今後用這份binlog來恢復數據庫,實際上恢復出來的數據就會多一次修改記錄.
看吧,如果這裏不用兩階段提交的話,就有一定概率會出現兩份日誌不一致的情況,從而導致後續用日誌來恢復回來的數據出現錯漏.

5.總結

本文從mysql的日誌系統出發,介紹了更新語句的執行流程.以及mysql崩潰後用來恢復的日誌記錄.
此外還可以優化兩個參數:
redo log 用於保證 crash-safe ,**innodb_flush_log_at_trx_commit **這個參數設置成 1 的時候,表示每次事務的 redo log 都直接持久化到磁盤.這個參數我建議你設置成 1,這樣可以保證 MySQL 異常重啓之後數據不丟失。
**sync_binlog **這個參數設置成 1 的時候,表示每次事務的 binlog 都持久化到磁盤。這個參數我也建議你設置成 1,這樣可以保證 MySQL 異常重啓之後 binlog 不丟失.並且這兩個參數基本不會影響執行速度,能夠提高數據庫的可靠性~

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