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

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

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


往期推薦
聽(tīng)說(shuō)你對(duì)explain 很懂?
面試官:你說(shuō)說(shuō)一條查詢(xún)SQL的執(zhí)行過(guò)程?| 文末送書(shū)
