面試官:你說說一條更新SQL的執(zhí)行過程?
在上一篇《面試官:你說說一條查詢SQL的執(zhí)行過程?》中描述了Mysql的架構分層,通過解析器、優(yōu)化器和執(zhí)行引擎完成一條SQL查詢的過程,那這一篇續(xù)上繼續(xù)說明一條更新SQL的執(zhí)行過程。
對于一個SQL語句的更新來說,前面的流程都可以說類似的,通過解析器進行語法分析,優(yōu)化器優(yōu)化,執(zhí)行引擎去執(zhí)行,這個都沒有什么問題,重點在于多了一點東西,那就是redo_log、undo_log和binlog。
執(zhí)行流程大致如下:
首先客戶端發(fā)送請求到服務端,建立連接。 服務端先看下查詢緩存,對于更新某張表的SQL,該表的所有查詢緩存都失效。 接著來到解析器,進行語法分析,一些系統(tǒng)關鍵字校驗,校驗語法是否合規(guī)。 然后優(yōu)化器進行SQL優(yōu)化,比如怎么選擇索引之類,然后生成執(zhí)行計劃。 執(zhí)行引擎去存儲引擎查詢需要更新的數(shù)據(jù)。 存儲引擎判斷當前緩沖池中是否存在需要更新的數(shù)據(jù),存在就直接返回,否則去從磁盤加載數(shù)據(jù)。 執(zhí)行引擎調用存儲引擎API去更新數(shù)據(jù)。 存儲引擎更新數(shù)據(jù),同時寫入undo_log、redo_log信息。 執(zhí)行引擎寫binlog,提交事務,流程結束。
可以看到相比于查詢流程,實際上更新多了關于undo_log和redo_log的流程,接下來再具體探討一下這幾個流程的執(zhí)行過程是什么樣子。

redo_log
redo_log按照字面翻譯稱為重做日志,是InnoDB存儲引擎特有的,用于保證事務的原子性和持久性。怎么理解呢?簡單來說就是保存我們執(zhí)行的更新語句的記錄,如果服務器或者Mysql宕機,通過redo_log可以恢復更新的數(shù)據(jù)。
按照上述流程來舉例的話,比如update user set age=20 where id=1這樣的簡單更新SQL,我們不管執(zhí)行引擎怎么拿到的數(shù)據(jù),不管是從緩沖池拿的還是磁盤拿到的,這條現(xiàn)在數(shù)據(jù)都在緩沖池里面,然后去緩沖池的數(shù)據(jù)把age改成10。
緩沖池內存中的數(shù)據(jù)已經(jīng)更新好了,那么接下來就該開始寫redo_log了,只是redo_log也不是直接寫文件的,一般都是這樣對吧,直接寫的話性能太差了,所以就有redo_log_buffer叫做redo_log緩沖。

在寫redo_log的時候先把數(shù)據(jù)寫到redo_log緩沖區(qū),然后異步寫入磁盤,很顯然,極端情況下會有丟失數(shù)據(jù)的可能。
控制這個刷盤策略的的參數(shù)叫做innodb_flush_log_at_trx_commit。
這個參數(shù)有3個值:0|1|2,默認的話是1。
0代表提交事務時不會寫入磁盤,這樣的話性能當然最好,但是在Mysql宕機的情況會丟失上一秒的事務的數(shù)據(jù)。
1代表提交事務一定會進行一次刷盤,同步當然性能最差,但是也最安全。
2代表寫入文件系統(tǒng)的緩存,不進行刷盤。這個選項性能略差于1,Mysql宕機的話對數(shù)據(jù)沒有任何影響,只有在操作系統(tǒng)宕機才會丟失數(shù)據(jù),這種情況下默認Mysql每秒會執(zhí)行一次刷盤。
使用0或者2雖然提高了性能,但是變相的也喪失了事務的持久性。
undo_log
重做日志保證了事務的持久性,保證能夠在宕機后恢復事務的數(shù)據(jù),那么另外一種情況就是事務在需要回滾的時候怎么辦?這時候就是undo_log的作用了,它保證了事務的一致性。
對于undo_log來說,簡單理解就是做了逆向操作。
比如insert一條數(shù)據(jù),就對應生成delete,update語句則生成相反的更新語句,這樣做到將數(shù)據(jù)修改回之前的狀態(tài)。
binlog
binlog稱為二進制日志,大家都很熟悉,記錄了改變數(shù)據(jù)庫的那些SQL語句,對于這里來說,更新語句當然是了。
通過不同于redo_log是獨屬于存儲引擎獨有的東西,binlog則是Mysql本身產(chǎn)生的日志。
不同于redo_log是物理日志,binlog和undo_log都屬于邏輯日志。
這有什么區(qū)別呢?
簡單來說,邏輯日志可以認為就是存儲的SQL本身,而物理日志看看redo_log存儲的是啥就知道了,關于page_id頁ID,offset偏移量啊這些東西,記錄的是對頁的修改。
另外物理日志可以保證冪等性,而邏輯日志則不一定能,除非本身SQL就是冪等的。
上面我們提到了redo_log的刷盤策略,binlog就和它非常類似了,控制參數(shù)是sync_binlog。
默認值為0,相當于是innodb_flush_log_at_trx_commit的值為2,由文件系統(tǒng)控制,同樣如果服務器宕機,binlog丟失,當然我們也可以改成1,就和redo_log的效果是一樣,每1次事務提交都同步寫入磁盤。
事務
為了保證寫redo_log和binlog的一致性,實際采用了二階段提交的方式。
prepare階段:根據(jù)innodb_flush_log_at_trx_commit設置的刷盤策略決定是否寫入磁盤,標記為prepare狀態(tài)。
commit階段:寫入binlog日志,事務標記為提交狀態(tài)。
總結


往期推薦
面試官:你說說一條查詢SQL的執(zhí)行過程?| 文末送書
