關(guān)鍵字
日志、索引
這一章是專欄老師的答疑課逗威,在這一節(jié)中,主要解決了一些關(guān)于日志和索引的疑惑岔冀。
日志相關(guān)問題
在第二篇文章中凯旭,講到了 binlog 和 redo log,這兩個(gè)日志使用了兩階段提交使套。這樣的方法可以解決崩潰恢復(fù)的問題罐呼。在這里,就詳細(xì)分析一下在 MySQL 發(fā)生異常重啟的時(shí)候侦高,是怎么保證數(shù)據(jù)完整性的嫉柴。
首先看一下在第二篇文章中的圖:在這里,我們就分析一下奉呛,在兩階段提交的不同時(shí)刻计螺,MySQL 異常重啟會(huì)出現(xiàn)什么現(xiàn)象。
如果在 時(shí)刻A 發(fā)生重啟瞧壮,也就是 redo log 處于 prepare 階段之后登馒、寫 binlog 之前發(fā)生了崩潰。此時(shí)咆槽,由于 binlog 還沒寫陈轿,redo log 沒提交,所以在恢復(fù)的時(shí)候,這個(gè)事務(wù)會(huì)回滾麦射。
如果 時(shí)刻B 發(fā)生重啟蛾娶,也就是在 binlog 寫完,redo log 還沒有 commit 時(shí)發(fā)生了崩潰潜秋。此時(shí)有兩個(gè)判斷規(guī)則:
1.如果 redo log 里的事務(wù)完整蛔琅,即已經(jīng)有了 commit,則直接提交半等。
2.如果 redo log 的事務(wù)只有 prepare,則需要判斷 binlog 是否存在且完整呐萨。如果完整杀饵,提交事務(wù);如果不完整谬擦,回滾切距。
在 時(shí)刻B,已經(jīng)有了完整的 binlog惨远,崩潰恢復(fù)的事務(wù)會(huì)被提交谜悟。
追問:MySQL 怎么知道 binlog 是否完整?
答:一個(gè)事務(wù)的 binlog 是有完整格式的北秽,所以葡幸,MySQL 有辦法驗(yàn)證事務(wù) binlog 的完整性。
追問:redo log 和 binlog 是如何關(guān)聯(lián)起來的贺氓?
答:它們有一個(gè)共同的字段蔚叨,XID。崩潰恢復(fù)的時(shí)候辙培,會(huì)順序掃描 redo log:
- 如果 redo log 中既有 prepare 又有 commit 蔑水,就直接提交。
- 如果只有 prepare扬蕊,而沒有 commit搀别,則需要拿著 redo log 中的 XID 去 binlog 中尋找對應(yīng)事務(wù)。
追問:prepare 的 redo log + 完整的 binlog --> 重啟后提交這個(gè)事務(wù)尾抑。為什么不設(shè)置為回滾歇父?
答:在 時(shí)刻B,binlog 已經(jīng)寫入了再愈,之后就會(huì)被從庫(或用這個(gè) binlog 恢復(fù)出來的庫)使用庶骄。所以,在主庫也要提交這個(gè)事務(wù)践磅,以保持主庫和備庫的數(shù)據(jù)一致性单刁。
追問:為什么要這樣設(shè)計(jì)兩階段提交呢?為什么不先寫完 redo log,再寫 binlog羔飞。崩潰恢復(fù)的時(shí)候肺樟,要求兩個(gè)日志都完整才可以回復(fù)。是不是也是一樣的邏輯逻淌?
答:不是的么伯,對于 InnoDB 引擎來說,如果 redo log 提交完成了卡儒,事務(wù)就不能回滾(如果這還允許回滾田柔,就可能覆蓋掉別的事務(wù)的更新)。而如果 redo log 直接提交骨望,然后 binlog 寫入的時(shí)候失敗硬爆,InnoDB 又回滾不了,數(shù)據(jù)和 binlog 日志又不一致了擎鸠。
所以缀磕,只有當(dāng)每個(gè)人都說“我 OK ”的時(shí)候,再一起提交劣光。
追問:為什么不能用 binlog 既支持崩潰回復(fù)袜蚕,又支持歸檔呢?
注:言下之意是:只保留 binlog绢涡,然后可以把提交流程改成這樣:… -> “數(shù)據(jù)更新到內(nèi)存” -> “寫 binlog” -> “提交事務(wù)”牲剃,是不是也可以提供崩潰恢復(fù)的能力?
答:不可以雄可。這有歷史原因和實(shí)現(xiàn)上的原因兩個(gè)方面颠黎。
歷史原因:InnoDB 并不是 MySQL 的原生引擎。MySQL 的原生 MyISAM 引擎并不支持崩潰恢復(fù)滞项。
InnoDB 作為插件加入 InnoDB 之后狭归,才通過 redo log 提供了崩潰回復(fù)的功能。
實(shí)現(xiàn)上的原因:在下面的圖中文判,沒有 redo log过椎,如果在指定的地方發(fā)生崩潰,會(huì)出現(xiàn)一些問題:
在發(fā)生 crash 時(shí)戏仓,binlog2 寫完了疚宇,但是 事務(wù)2 還沒有 commit。
重啟后赏殃,事務(wù)2 會(huì)進(jìn)行回滾敷待,然后使用 binlog2 補(bǔ)回來;但是對于 事務(wù)1 來說仁热,因?yàn)橐呀?jīng) commit榜揖,所以并不會(huì)使用 binlog1。
InnoDB 使用的是 WAL,執(zhí)行事務(wù)的時(shí)候举哟,寫完內(nèi)存和日志思劳,事務(wù)就算完成了,注意妨猩,此時(shí)內(nèi)存中的數(shù)據(jù)并不一定已經(jīng)刷到了磁盤中潜叛。
所以,如果在圖中的位置發(fā)生崩潰壶硅,事務(wù)1 可能會(huì)丟失威兜,而且是數(shù)據(jù)頁級別的丟失。所以庐椒,目前來說椒舵,binlog 還不能支持崩潰恢復(fù)。
追問:能不能只用 redo log扼睬,不用 binlog逮栅?
答:如果只從崩潰恢復(fù)的角度來講是可以的悴势。關(guān)掉 binlog 之后系統(tǒng)依然是 crash-safe 的窗宇。
但是,binlog 擁有很多其它功能特纤,比如歸檔军俊、比如在異構(gòu)系統(tǒng)中消費(fèi) binlog 來更新自己的數(shù)據(jù)。如果關(guān)掉 binlog捧存,這些功能都無法使用粪躬。
總之,很多系統(tǒng)機(jī)制都依賴 binlog昔穴,所以從生態(tài)的角度來看镰官,binlog 必不可少。
追問:redo log 設(shè)置多大吗货?
答:如果 redo log 設(shè)置太小泳唠,會(huì)很快被寫滿,然后觸發(fā)強(qiáng)行刷 redo log宙搬,這會(huì)降低性能笨腥。
所以,如果你的磁盤足夠勇垛,就不要太小氣了脖母,直接將 redo log 設(shè)置為 4 個(gè)文件、每個(gè)文件 1GB 吧闲孤。
追問:數(shù)據(jù)的最終落盤谆级,是從 redo log 更新還是從 buffer pool 更新呢?
答:實(shí)際上,redo log 并沒有記錄數(shù)據(jù)頁的完整數(shù)據(jù)哨苛,所以最終落盤鸽凶,不可能是由 redo log 更新過去的。
- 臟頁落盤和 redo log 毫無關(guān)系建峭。
- 在崩潰恢復(fù)中玻侥,InnoDB 如果判斷一個(gè)數(shù)據(jù)頁可能丟失更新,就會(huì)將它讀到內(nèi)存中亿蒸,然后用 redo log 更新內(nèi)存內(nèi)容凑兰。更新后,就又變成了臟頁落盤的問題边锁,這就和 redo log 無關(guān)了姑食。
小結(jié)
這是一篇答疑文章,主要回答了日志的相關(guān)問題茅坛。實(shí)際上音半,專欄文章還講到了一個(gè)比較復(fù)雜的問題,但是我并沒有理解那個(gè)問題贡蓖,所以在這里就沒有寫出曹鸠。如果對之后的問題感興趣,可以到專欄中找一找斥铺。
上期問題
上期的問題是彻桃,用一個(gè)計(jì)數(shù)表記錄一個(gè)業(yè)務(wù)表的總行數(shù),在往業(yè)務(wù)表插入數(shù)據(jù)的時(shí)候晾蜘,需要給計(jì)數(shù)值加 1邻眷。
邏輯實(shí)現(xiàn)上是啟動(dòng)一個(gè)事務(wù),執(zhí)行兩個(gè)語句:
- insert into 數(shù)據(jù)表剔交;
- update 計(jì)數(shù)表肆饶,計(jì)數(shù)值加 1。
從系統(tǒng)并發(fā)能力的角度考慮岖常,怎么安排這兩個(gè)語句的順序驯镊。
解答:
并發(fā)系統(tǒng)性能的角度考慮,應(yīng)該先插入操作記錄腥椒,再更新計(jì)數(shù)表阿宅。
知識(shí)點(diǎn)在《行鎖功過:怎么減少行鎖對性能的影響?》
因?yàn)楦掠?jì)數(shù)表涉及到行鎖的競爭笼蛛,先插入再更新能最大程度地減少事務(wù)之間的鎖等待洒放,提升并發(fā)度。
本期思考
我們創(chuàng)建了一個(gè)簡單的表 t滨砍,并插入一行往湿,然后對這一行做修改妖异。
mysql> CREATE TABLE `t` (
`id` int(11) NOT NULL primary key auto_increment,
`a` int(11) DEFAULT NULL
) ENGINE=InnoDB;
insert into t values(1,2);
這時(shí)候,表 t 里有唯一的一行數(shù)據(jù) (1,2)领追。假設(shè)他膳,我現(xiàn)在要執(zhí)行:
mysql> update t set a=2 where id=1;
你會(huì)看到這樣的結(jié)果:結(jié)果顯示,匹配 (rows matched) 了一行绒窑,修改 (Changed) 了 0 行棕孙。
僅從現(xiàn)象上看,MySQL 內(nèi)部在處理這個(gè)命令的時(shí)候些膨,可以有以下三種選擇:
- 更新都是先讀后寫的蟀俊,MySQL 讀出數(shù)據(jù),發(fā)現(xiàn) a 的值本來就是 2订雾,不更新肢预,直接返回,執(zhí)行結(jié)束洼哎;
- MySQL 調(diào)用了 InnoDB 引擎提供的“修改為 (1,2)”這個(gè)接口烫映,但是引擎發(fā)現(xiàn)值與原來相同,不更新噩峦,直接返回锭沟;
- InnoDB 認(rèn)真執(zhí)行了“把這個(gè)值修改成 (1,2)"這個(gè)操作,該加鎖的加鎖壕探,該更新的更新冈钦。
你覺得實(shí)際情況會(huì)是以上哪種呢郊丛?你可否用構(gòu)造實(shí)驗(yàn)的方式李请,來證明你的結(jié)論?進(jìn)一步地厉熟,可以思考一下导盅,MySQL 為什么要選擇這種策略呢?
以上就是本節(jié)內(nèi)容揍瑟,愿你能解決疑問白翻。
注:本文章的主要內(nèi)容來自我對極客時(shí)間app的《MySQL實(shí)戰(zhàn)45講》專欄的總結(jié),我使用了大量的原文绢片、代碼和截圖滤馍,如果想要了解具體內(nèi)容,可以前往極客時(shí)間