我們把MySQL的基本執(zhí)行鏈路在拿過來進(jìn)行看下,??可以確定的是,?查詢的那一套流程, 更新語句也會走一遍全蝶。
在一個表上進(jìn)行更新的時候,跟這個表有關(guān)的所有緩存,都會失效抑淫,這條語句會把表上的所有緩存結(jié)果都清空绷落,所以我們建議不查詢緩存的原因。
接下來分析器會知道這是一條更新語句始苇,如果更新的字段有索引砌烁,優(yōu)化器會采用這個字段的索引,執(zhí)行器具體負(fù)責(zé)執(zhí)行催式,找到這一行數(shù)據(jù)函喉,然后更新。
與查詢不一樣的是荣月,更新流程會有兩個重要的日志模塊管呵。redo log?(重做日志) 和 binlog (歸檔日志)。
重要的日志模塊 一哺窄、redo log在講redo log 之前捐下,?我們要來回顧一篇文章《孔乙己》。
在這篇文章里萌业,有一個酒店掌柜坷襟,有一塊粉板,專門用來記錄客人賒賬的記錄生年。如果賒賬的人不多婴程,掌柜可以把姓名和金額記錄到粉板上。但是如果人多了晶框,粉板記不下了排抬,掌柜就要放下手頭的工作,記錄到專門的賬本上授段。
如果有人賒賬蹲蒲,掌柜一般有兩種做法:
????1、直接翻出賬本侵贵,進(jìn)行記錄賒賬人和金額届搁;
????2、先記錄到粉板窍育,在不忙的時候記錄到賬本卡睦。
在生意紅火的時候,掌柜會選擇后者漱抓,因為前者過于麻煩表锻,效率太低。
在MySQL中乞娄,MySQL的設(shè)計者們瞬逊,也采用了粉板的思路來提升效率显歧,而粉板與賬本的配合過程中,其實就是MySQL中的WAL技術(shù)(write-ahead-logging)确镊,關(guān)鍵點就是 先寫日志士骤,再寫磁盤。也就是先寫粉板蕾域,再寫賬本拷肌。
也就是說,當(dāng)一條更新語句過來時旨巷,InnoDB引擎會把記錄記錄到redo?log(粉板)中巨缘,并更新內(nèi)存,這個時候就算更新完成了契沫。InnoDB引擎會在合適的時候带猴,進(jìn)行更新到磁盤中。
redo log 的大小是固定的懈万,比如可以一組配置4個文件拴清,每個文件大小是1G,那么這個redo log 可以記錄4G会通。從頭開始寫口予,寫到末尾就又回到開頭循環(huán)寫,如下面這個圖所示涕侈。
write pos 是當(dāng)前記錄的位置沪停,一邊寫一邊后移,寫到第 3 號文件末尾后就回到 0 號文件開頭裳涛。checkpoint 是當(dāng)前要擦除的位置木张,也是往后推移并且循環(huán)的,擦除記錄前要把記錄更新到數(shù)據(jù)文件端三。
write pos 和 checkpoint 中間空著的部分舷礼,就是可以寫入的部分,如果write pos 追上了 checkpoint 郊闯,代表寫滿了妻献,就需要停下擦除一部分,把checkpoint推進(jìn)一下团赁。
有了 redo log育拨,InnoDB 就可以保證即使數(shù)據(jù)庫發(fā)生異常重啟,之前提交的記錄都不會丟失欢摄,這個能力稱為 crash-safe熬丧。redo log 是屬于?InnoDB特有的日志。
二怀挠、binlog我們之前學(xué)習(xí)知道锹引,MySQL有兩塊矗钟,一塊是server層唆香,一塊是引擎層嫌变。也知道,redo log?是InnoDB特有的日志躬它,server層也有自己的日志腾啥,就是binlog(歸檔日志)。
為什么會有兩份日志呢冯吓?
因為在最開始倘待,MySQL并沒有innodb引擎。MySQL自帶的是MyISAM组贺,但是 MyISAM 沒有 crash-safe 的能力凸舵,binlog 日志只能用于歸檔。而 InnoDB 是另一個公司以插件形式引入 MySQL 的失尖,既然只依靠 binlog 是沒有 crash-safe 能力的啊奄,所以 InnoDB 使用另外一套日志系統(tǒng)——也就是 redo log 來實現(xiàn) crash-safe 能力。
這兩種日志有以下三點不同掀潮。
?? ?1菇夸、redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現(xiàn)的仪吧,所有引擎都可以使用庄新。
??? 2、redo log 是物理日志薯鼠,記錄的是“在某個數(shù)據(jù)頁上做了什么修改”择诈;binlog 是邏輯日志,記錄的是這個語句的原始邏輯出皇,比如“給 ID=2 這一行的 c 字段加 1 ”羞芍。
????3、redo log 是循環(huán)寫的恶迈,空間固定會用完涩金;binlog 是可以追加寫入的∠局伲“追加寫”是指 binlog 文件寫到一定大小后會切換到下一個步做,并不會覆蓋以前的日志。
有了對這兩個日志的一些概念性理解奈附,我們應(yīng)該能知道流程是如何進(jìn)行執(zhí)行的了全度。
先找到引擎取出這一行,引擎通過B+ TREE找到這一行斥滤。如果這個數(shù)據(jù)頁本就在內(nèi)存将鸵,直接返回勉盅。否則從磁盤讀入內(nèi)存,然后返回顶掉。
執(zhí)行器拿到引擎給的數(shù)據(jù)草娜,做上邏輯操作,調(diào)用引擎接口寫入數(shù)據(jù)痒筒。
引擎將新數(shù)據(jù)更新到內(nèi)存宰闰,同時將這個操作記錄到redo log 中,此時redo log 處于 prepare狀態(tài)簿透。然后告知執(zhí)行器執(zhí)行完成移袍,可以提交(commit)事務(wù)。
執(zhí)行器生成這個操作的binlog老充,把binlog寫入磁盤葡盗。調(diào)用提交事務(wù)接口,把剛剛的binlog改成提交狀態(tài)啡浊。
二觅够、將redo log拆為prepare 和 commit?兩階段引發(fā)的思考為什么要有兩階段提交啊虫啥?
這是為了讓兩份日志之間保持邏輯一致蔚约。要說明這個問題,就要想到我們開始的那個問題涂籽,怎么恢復(fù)到半個月內(nèi)任意一秒鐘的數(shù)據(jù)苹祟?。
因為binlog會記錄所有的邏輯操作评雌,并且采用?“追加寫”的方式树枫。如果DBA承諾可以恢復(fù),說明一定是備份了半個月的所有的binlog景东,同時系統(tǒng)會定期整庫備份砂轻。
1. 首先找個臨時庫,完全空的?
2. 找最近的一份全量備份,全量備份的時間點比如為202-01-31?00:00:00?
3. 從202-01-31?00:00:00開始找出到誤刪前的增量binlog日志,(需要將其誤操作記錄刪除, 不然還是會刪除掉之前的記錄)并執(zhí)行到臨時庫 這樣就保證了臨時庫和誤刪前的庫一致了。
說了這么多斤吐,那為什么需要兩階段提交呢搔涝?
由于 redo log 和 binlog 是兩個獨立的邏輯,如果不用兩階段提交和措,要么就是先寫完 redo log 再寫 binlog庄呈,或者采用反過來的順序。我們看看這兩種方式會有什么問題派阱。
1诬留、先寫 redo log 后寫 binlog。假設(shè)在 redo log 寫完,binlog 還沒有寫完的時候文兑,MySQL 進(jìn)程異常重啟盒刚。由于我們前面說過的,redo log 寫完之后绿贞,系統(tǒng)即使崩潰因块,仍然能夠把數(shù)據(jù)恢復(fù)回來,所以恢復(fù)后這一行 c 的值是 1樟蠕。但是由于 binlog 沒寫完就 crash 了贮聂,這時候 binlog 里面就沒有記錄這個語句。因此寨辩,之后備份日志的時候,存起來的 binlog 里面就沒有這條語句歼冰。然后你會發(fā)現(xiàn)靡狞,如果需要用這個 binlog 來恢復(fù)臨時庫的話,由于這個語句的 binlog 丟失隔嫡,這個臨時庫就會少了這一次更新甸怕,恢復(fù)出來的這一行 c 的值就是 0,與原庫的值不同腮恩。
1梢杭、先寫 binlog 后寫 redo log。秸滴。如果在 binlog 寫完之后 crash武契,由于 redo log 還沒寫,崩潰恢復(fù)以后這個事務(wù)無效荡含,所以這一行 c 的值是 0咒唆。但是 binlog 里面已經(jīng)記錄了“把 c 從 0 改成 1”這個日志。所以释液,在之后用 binlog 來恢復(fù)的時候就多了一個事務(wù)出來全释,恢復(fù)出來的這一行 c 的值就是 1,與原庫的值不同误债。
可以看到浸船,如果不使用“兩階段提交”,那么數(shù)據(jù)庫的狀態(tài)就有可能和用它的日志恢復(fù)出來的庫的狀態(tài)不一致寝蹈。
簡單說李命,redo log 和 binlog 都可以用于表示事務(wù)的提交狀態(tài),而兩階段提交就是讓這兩個狀態(tài)保持邏輯上的一致躺盛。