MySQL存儲引擎如何完成一條更新語句的執行

假設咱們有一條SQL語句是這樣的:mysql

update t_user set name='月伴飛魚' where id=1;

那麼咱們先想一下這條SQL語句是如何執行的?web

首先確定是咱們的系統經過一個數據庫鏈接發送到了MySQL上,而後確定會通過SQL接口、解析器、優化器、執行器幾個環節,解析SQL語句,生成執行計劃,接着去由執行器負責這個計劃的執行,調用InnoDB存儲引擎的接口去執行。sql

大體會走下圖的這個流程數據庫

咱們就來探索一下這個存儲引擎裏的架構設計,以及如何基於存儲引擎完成一條更新語句的執行緩存

緩衝池

InnoDB存儲引擎中有一個很是重要的放在內存裏的組件,就是緩衝池(Buffer Pool),這裏面會緩存不少的數據, 以便於之後在查詢的時候,萬一你要是內存緩衝池裏有數據,就能夠不用去查磁盤了微信

因此當咱們的InnoDB存儲 引擎要執行更新語句的時候 ,好比對「id=1」這一行數據,他其實會先將「id=1」這一行數據看看是否在緩衝池裏,若是不在的 話,那麼會直接從磁盤裏加載到緩衝池裏來,並且接着還會對這行記錄加獨佔鎖。架構

由於咱們想一下,在咱們更新「id=1」這一行數據的時候,確定是不容許別人同時更新的,因此必需要對這行記錄加 獨佔鎖併發

undo日誌文件

如何讓你更新的數據能夠回滾?異步

接着下一步,假設「id=1」這行數據的name原來是「周星星」,如今咱們要更新爲「月伴飛魚」,那麼此時咱們得先 把要更新的原來的值「周星星」和「id=1」這些信息,寫入到undo日誌文件中去。編輯器

數據庫中,若是咱們執行一個更新語句,要是他是在一個事務裏的話,那麼事 務提交以前咱們都是能夠對數據進行回滾的,也就是把你更新爲「月伴飛魚」的值回滾到以前的「周星星」去。

因此爲了考慮到將來可能要回滾數據的須要,這裏會把你更新前的值寫入undo日誌文件,咱們看下圖。

更新buffer pool中的緩存數據

這裏所謂的更新內存緩衝池裏的數據,意思就是把內存裏的「id=1」這行數據的name字段修改成「月伴飛魚」

當咱們把要更新的那行記錄從磁盤文件加載到緩衝池,同時對他加鎖以後,並且還把更新前的舊值寫入undo日誌文件 以後,咱們就能夠正式開始更新這行記錄了,更新的時候,先是會更新緩衝池中的記錄,此時這個數據就是髒數據 了。

那麼爲何說此時這行數據就是髒數據了呢?

由於這個時候磁盤上「id=1」這行數據的name字段仍是「周星星」,可是內存裏這行數據已經被修改了,因此 就會叫他是髒數據。

redo log

接着咱們來思考一個問題,按照上圖的說明,如今已經把內存裏的數據進行了修改,可是磁盤上的數據還沒修改

那麼此時萬一MySQL所在的機器宕機了,必然會致使內存裏修改過的數據丟失,這可怎麼辦呢?這個時候,就必需要把對內存所作的修改寫入到一個Redo Log Buffer裏去,這也是內存裏的一個緩衝區,是用來存 放redo日誌的

所謂的redo日誌,就是記錄下來你對數據作了什麼修改,好比對「id=1這行記錄修改了name字段的值爲「月伴飛魚」,這 就是一個日誌。咱們先看下圖

這個redo日誌實際上是用來在MySQL忽然宕機的時候,用來恢復你更新過的數據的

提交事務的時候將redo日誌寫入磁盤中

接着咱們想要提交一個事務了,此時就會根據必定的策略把redo日誌從redo log buffer裏刷入到磁盤文件裏去。

此時這個策略是經過innodb_flush_log_at_trx_commit來配置的,他有幾個選項。當這個參數的值爲0的時候,那麼你提交事務的時候,不會把redo log buffer裏的數據刷入磁盤文件的,此時可能你都 提交事務了,結果mysql宕機了,而後此時內存裏的數據所有丟失。至關於你提交事務成功了,可是因爲MySQL忽然宕機,致使內存中的數據和redo日誌都丟失了

當這個參數的值爲1的時候,你提交事務的時候,就必須把redo log從內存刷入到磁盤文件裏去,只要事務提交成功,那麼redo log就 必然在磁盤裏了

那麼只要提交事務成功以後,redo日誌必定在磁盤文件裏,此時你確定會有一條redo日誌說了,「我此時對哪一個數據作了一個什麼修 改,好比name字段修改成月伴飛魚了」。

而後哪怕此時buffer pool中更新過的數據還沒刷新到磁盤裏去,此時內存裏的數據是已經更新過的「name=月伴飛魚」,而後磁盤上的數 據仍是沒更新過的「name=周星星」。

此時若是說提交事務後處於上圖的狀態,而後mysql系統忽然崩潰了,此時會如何?會丟失數據嗎?

確定不會啊,由於雖然內存裏的修改爲name=月伴飛魚的數據會丟失,可是redo日誌裏已經說了,對某某數據作了修改 name=月伴飛魚。

因此此時mysql重啓以後,他能夠根據redo日誌去恢復以前作過的修改

最後來看看,若是innodb_flush_log_at_trx_commit參數的值是2呢?

他的意思就是,提交事務的時候,把redo日誌寫入磁盤文件對應的os cache緩存裏去,而不是直接進入磁盤文件,可 能1秒後纔會把os cache裏的數據寫入到磁盤文件裏去。

這種模式下,你提交事務以後,redo log可能僅僅停留在os cache內存緩存裏,沒實際進入磁盤文件,萬一此時你要 是機器宕機了,那麼os cache裏的redo log就會丟失,一樣會讓你感受提交事務了,結果數據丟了

三種redo日誌刷盤策略到底選擇哪種?

innodb_flush_log_at_trx_commit=0 提交事務的時候,不會將內存中的redo log刷入磁盤

優勢,純內存操做速度快,缺點,redo日誌沒有落地磁盤,若是提交事務的一瞬間,MySQL宕機,那麼若是是修改數據,內存數據沒了,磁盤也沒來的及更新,就丟失了本次修改操做。

innodb_flush_log_at_trx_commit=1,提交事務以前必定會將redo log 刷入磁盤

優勢,事務提交以前,事務操做log必定刷入磁盤,事務成功,磁盤必定有redo日誌,若是事務提交成功,內存修改,磁盤尚未更新,徹底能夠讀取redo日誌恢復數據。缺點,寫磁盤確實會消耗不少性能,若是是高併發,大量寫入,必定會影響寫入性能,吞吐量和處理時間都會影響到。

innodb_flush_log_at_trx_commit=2,將redo日誌刷入OS cache,間隔可能一秒寫入磁盤。方案鑑於一和二方案之間。

優勢,利用OS cache去緩存部分日誌,能夠提升吞吐量,間隔時間,異步刷入磁盤。缺點,提交事務以後,可能redo日誌還在cache中。此時,日誌存在丟失的風險。

三種方案,第一種方案適用於,容許不重要的數據,可是大批量插入的場景,可能丟失,好比一些大批量的任務執行日誌上報的數據。

方案二適用於數據不可丟失的插入更新,好比訂單,用戶等核心數據。

方案三,適用於高併發插入,容許必定數據丟失,可是大部分可靠的場景,好比用戶行爲日誌,APP異常上報等。

通常建議redo日誌刷盤策略設置爲1,保證事務提交以後,數據絕對不能丟失,MySQL中這個參數默認值爲1

參考:

從零開始帶你成爲MySQL實戰優化高手

國慶節快樂

掃描二維碼

獲取更多精彩

月伴飛魚

本文分享自微信公衆號 - 月伴飛魚(gh_c4183eee9eb9)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索