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

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

之前你可能經常聽DBA同事說,MySQL可以恢復到半個月內任意一秒的狀態,驚歎的同時,你是不是心中也會不免會好奇,這是怎樣做到的呢?

這個需要從一個表,一條更新語句說起。 假設創建一個表,有一個主鍵 ID和 一個整型字段 C:

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

現在要將 ID = 2 這一行更新

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

MySQL邏輯架構圖
執行語句前,需要連接器的工作,在一個表上有更新的時候,跟這個表有關的緩存會失效,所以這條語句會把表 T 上所有緩存結果都清空,這也是爲啥不推薦使用查詢緩存的原因。
分析器通過詞法和語法分析這條更新語句,優化器決定要使用 ID 這個索引,然後執行器具體執行,找到這一條,然後更新。 跟查詢流程不一樣的是,更新流程還涉及兩個重要的日誌模塊, redo log(重做日誌)和 binlog(歸檔日誌)。

重做日誌 redo log 是啥?

redo Log 是 InnoDB 引擎特有的日誌。
如果每一次更新操作都需要寫進磁盤,然後磁盤要找到那條記錄,然後再更新,整個過程 IO 成本很高,查找成本很高。MySQL 採用了什麼方式提高更新效率呢?

MySQL 採用 WAL 技術,Write Ahead Loging,關鍵點是先寫日誌再寫磁盤,具體執行如下:當有一條記錄需要更新的時候,InnoDB 引擎會先把記錄寫到 redo log裏,並更新內存,這個時候更新就算完事了。 當 InnoDB 引擎會在適當的時候,將這個操作記錄更新到磁盤裏面,這個更新一般是在空閒的時候做。

InnoDB 的 redo log 是固定大小的,入股可以配置一組4個文件,每個文件大小是 1G,那麼可以記錄 4GB 的操作。當滿了的時候寫入磁盤。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-I6OhNZAh-1589966449266)(https://imgkr.cn-bj.ufileos.com/6213857c-e847-46fa-b5f5-1538947f7de0.png)]

write pos 是當前記錄的位置,一邊寫一遍後移,相當於類似循環鏈表,寫到第3號文件末尾後就回到文件開頭。

checkpoint 是當前要擦除的位置,也是往後推移並且循環的,擦除記錄前要記錄更新到數據文件。

write pos 和checkpoint 是 redo log 中空閒的部分,可以記錄新的操作。 如果 write pos 追上了 checkpoint ,表示 redo log 滿了,這個時候不能再更新,需要擦除掉一些記錄,把 CheckPoint 推進。

什麼是 Crash-safe?

當數據庫發生異常重啓時,之前提交的記錄都不會丟失,這個能力叫做 crash-safe。

redo log用於保證 crash-safe能力。 innodb_flush_log_at_trx_comm這個參數設置成1的時候,表示每次事務的 redo log都直接持久化到磁盤。這個參數我建議你設置成1,這樣可以保證MySQL異常重啓之後數據不丟失。

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

日誌歸檔 binlog 是啥?

MySQL 整體看,一個是 Server 層,主要做的是 MySQL 工作層面的事情,還有一塊是存儲引擎層,負責存儲相關的具體事宜。 Redo Log 是 InnoDB 引擎特有的日誌,而 Server層也有自己的日誌,就是 binlog(歸檔日誌)。

爲啥要binlog 和 redolog 兩份日誌?

最開始的時候 MySQL 裏面沒有 InnoDB 引擎,MySQL 自帶的引擎是 MyISAM, 但是 MyISAM 沒有 crash-safe 能力,binlog 日誌只能用於歸檔,而 InnoDB 利用 redolog 可以實現 crash-safe 能力。

Redo Log 和 BinLog 的區別?

  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. 引擎將這行數據更新到內存中,同時將這些更新操作記錄到 redo log 裏面,此時 redo log 處於 prepare 狀態,然後告知執行器完成了,可以隨時提交事務。
  4. 執行生成了這個操作的 binlog ,並把 binlog 寫入磁盤。
  5. 執行器調用引擎提交事務的接口,引擎吧剛剛寫入的 redo log 改成提交 (commit)狀態,更新完成。

update內部流程

兩階段提交

redo log 的寫入拆成了兩個步驟:prepare 和 commit ,這就是“兩階段提交”,爲什麼必須有“兩階段提交”。這個是爲了讓binlog 和 redolog 兩者間的邏輯一致。
如果不採用兩階段提交,要麼寫 redo log 再寫binlog 或者,先寫binlog 再寫 redo log,會有什麼問題? 假設 ID =2 這一行數據,字段 c 的值是 0 現在要執行 update 字段 c + 1 操作。

  1. 先寫redo log ,然後寫 binlog ,如果 redo log 寫完,binlog 還沒有寫完的 MySQl 異常重啓,這個時候 redo 寫完了數據是可恢復了,這一行的數據c已經更新成1。 但是由於 binlog 還沒寫完就 crash 了,這個時候 binlog 裏面就沒有記錄這個語句,因此之後備份日誌的時候,存起來 binlog裏面就沒這條語句。這個時候,如果要 binlog 來恢復臨時庫 ,這條 binlog 沒寫入,那麼恢復出來的話,中一行的值就是 0,與原來的那個庫 1 是不同的。
  2. 先寫 binlog後寫 redo log。如果在 binlog寫完之後 crash,由於 redo log還沒寫,崩潰恢復以後這個事務無效,所以這一行c的值是0, 但是binlog裏面已經記錄了“把c從0改成1這個日誌。所以,在之後用binlog來恢復的時候就多了一個事務出來,恢復出來的這一行c的值就是與原庫的值不同。

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

如何讓數據庫恢復半個月內任意一秒的狀態?

binlog 記錄所有邏輯操作,並且採用“追加寫”的形式。如果 DBA 說 半個月內可恢復,說明保存最近半個月的所有 binlog ,同時系統會定期保存最近半個月的所有 binlog,同時系統會定期做整庫備份。

  • 首先,找到最近的一次全量備份,如果運氣好,可能就是昨天晚上的一個備份,從這個庫備份到臨時庫;
  • 然後,從備份的時間點開始,將備份的 binlog 依次取出來,重放中午誤刪表之前的那個時刻。

這樣流完成了數據庫恢復。

程序員開發者社區

微信公衆號

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