3.mysql更新語句執(zhí)行過程詳解

故事是這樣開始的层皱,很久很久以前伴箩,在一個(gè)月結(jié)(喝酒的人一個(gè)月結(jié)一次賬)酒店的老板是這樣記賬的:每次一個(gè)客戶進(jìn)來買酒识椰,老板都會(huì)找出賬本绝葡,然后呢再找到這個(gè)人,在這個(gè)人的名字下面記一下買酒的金額腹鹉、日期藏畅,記完之后把酒給到客戶。后來酒店的生意越來越好种蘸,老板發(fā)現(xiàn)每次客戶來都找到賬本再找到這個(gè)人名字墓赴,記錄賒賬信息,效率十分低下航瞭,忙不過來诫硕。于是老板想到一個(gè)新的辦法:他找來一個(gè)黑板,每次客戶來呢刊侯,先把這個(gè)人的賒賬信息記錄在黑板上章办,等到自己空閑的時(shí)候再把賒賬信息一條一條更新到賬本上。老板這么一搞,一天接待的客戶也多了藕届,這真是一個(gè)好辦法挪蹭。``

mysql更新語句會(huì)涉及到寫磁盤的過程,如果每次更新語句都去寫磁盤就像酒店老板每次找到賬本寫賒賬信息一樣休偶,那必然很影響mysql處理速度梁厉,更不可能在現(xiàn)在高并發(fā)的場景下滿足要求。為了提高處理速度踏兜,mysql實(shí)際上也是采用了先寫黑板再寫賬本的方法词顾,黑板和賬本的配合過程,就是mysql 中常說的WAL(Write-Ahead Logging)技術(shù)碱妆。其實(shí)就是先寫日志再寫磁盤肉盹。這里的日志在mysql中叫redo log,對(duì)應(yīng)的就是酒店老板的小黑板疹尾,磁盤對(duì)應(yīng)的就是酒店老板的賬本上忍。其實(shí)呢mysql 的更新過程不僅有redo log還涉及到binlog,那下面我們先介紹一下 redo log 纳本。

redo log

redo log記錄的是物理日志窍蓝,是對(duì)數(shù)據(jù)頁某個(gè)位置的修改,所以說redo log 會(huì)記修改的數(shù)據(jù)頁的編號(hào)(page no)饮醇。這里插一句 redo log 也會(huì)記錄LSN(log sequence number)它抱,LSN 是單調(diào)遞增的,每次寫入長度為length的redo log朴艰,LSN就會(huì)加length观蓄,來標(biāo)識(shí)每次redo log 的寫入位點(diǎn),數(shù)據(jù)頁也會(huì)記錄當(dāng)前頁最后一次修改的LSN祠墅,它記錄在數(shù)據(jù)頁的頭部侮穿,它的主要目的是用于在恢復(fù)數(shù)據(jù)時(shí)對(duì)比redolog日志的LSN號(hào)決定是否對(duì)該頁進(jìn)行恢復(fù)數(shù)據(jù),LSN把一個(gè)事務(wù)開始到恢復(fù)的過程串聯(lián)起來了毁嗦。前面說的LSN亲茅,checkpoint也是有記錄的,checkpoint位于redo log file 文件file_header 里面 狗准。innodb 引擎在寫redo log 的時(shí)候先把redo log 寫到 redo log buffer 中(redo log buffer 的大小由)克锣,寫的時(shí)候是一個(gè)一個(gè)的redo log block ,redo log block每個(gè)大小是512字節(jié)腔长,其結(jié)構(gòu)如下:

redo buffer 結(jié)構(gòu).png

其中l(wèi)og block中492字節(jié)的部分是log body袭祟,該log body的格式分為4部分:

  • redo_log_type:redo log的日志類型,占用1個(gè)字節(jié)捞附。
  • space:空間的ID巾乳,采用壓縮的方式后您没,占用的空間可能小于4字節(jié)。
  • page_no:頁的偏移量
  • redo_log_body 重做日志的數(shù)據(jù)部分胆绊,恢復(fù)時(shí)會(huì)調(diào)用相應(yīng)的函數(shù)進(jìn)行解析氨鹏。例如insert語句和delete語句寫入redo log的內(nèi)容是不一樣的。

在刷盤的時(shí)候压状,會(huì)將 redo log buffer (大小由innodb_log_buffer_size控制)中的日志塊寫入redo log file 中 就是我們經(jīng)常在/data 目錄中看到的以ib_logfile開頭的文件仆抵,ib_logfile 文件的大小由innodb_log_file_size 控制,個(gè)數(shù)由innodb_log_files_in_group 控制何缓,ib_logfile 文件之間的關(guān)系是他們是同屬于一個(gè)組肢础,文件之間通過鏈表鏈接 在組內(nèi)形成一個(gè)環(huán),就這樣覆蓋寫碌廓,實(shí)際上是這樣存在的:

redo log物理結(jié)構(gòu)

邏輯上是這樣存在的:

redo log 組邏輯結(jié)構(gòu)

這里也要說一下圖中 write poscheck point的含義:

  • write pos 它表示的是日志的當(dāng)前寫入位置,一邊寫一邊后移剩盒,當(dāng)寫到ib_logfile_3末尾的時(shí)候谷婆,再繼續(xù)寫到ib_logfile_0
  • checkpoint 是當(dāng)前要擦除的位置,也是往后移動(dòng)的辽聊,擦除前要把日志更新到數(shù)據(jù)文件中(就是磁盤中的數(shù)據(jù)頁)

如果checkpoint 追上write pos 纪挎,那么表示已經(jīng)沒有地方來寫日志了,這個(gè)時(shí)候不能再執(zhí)行更新跟匆,需要將checkpoint 往后移動(dòng)异袄,移動(dòng)的部分就是刷臟頁(這個(gè)過程在下一節(jié)講),有了 redo log玛臂,mysql就有了crash_safe 的能力烤蜕,就是說innodb 能夠保證數(shù)據(jù)庫發(fā)生異常重啟,數(shù)據(jù)不會(huì)丟失迹冤。

這里還有一個(gè)問題 relog buffer 里面的日志塊什么時(shí)候?qū)懭?redo log 文件呢(既ib_logfile文件)讽营?

這里要注意的是沒有提交事務(wù)的redo log 也是可能寫入到磁盤的,所以說我們分兩大類來討論寫盤問題

  • 事務(wù)已提交

    為了控制 redo log 的寫入策略泡徙,InnoDB 通過 innodb_flush_log_at_trx_commit 參數(shù)橱鹏,來控制redo log的寫入策略,它有三種可能取值:

    1. 設(shè)置為 0 的時(shí)候堪藐,表示每次事務(wù)提交時(shí)都只是把 redo log 留在 redo log buffer 中 ;
    2. 設(shè)置為 1 的時(shí)候莉兰,表示每次事務(wù)提交時(shí)都將 redo log 直接持久化到磁盤;
    3. 設(shè)置為 2 的時(shí)候礁竞,表示每次事務(wù)提交時(shí)都只是把 redo log 寫到 page cache糖荒。

    InnoDB,后臺(tái)有一個(gè)線程每1s 就會(huì)把redo buffer 里面的日志調(diào)用write寫到文件系統(tǒng)的page cache 里面苏章,然后調(diào)用fsyn持久化到磁盤寂嘉,因?yàn)槭聞?wù)在執(zhí)行過程中寫的日志都在 redo buffer 里面奏瞬,所以所一個(gè)未完成(未提交)的事務(wù)的日志是可能持久化到磁盤的

  • 事務(wù)未提交

    除了上面說的定時(shí)線程會(huì)將未提交的事務(wù)的日志持久化到磁盤外,還有兩種情況也會(huì)將未提交事務(wù)的日志持久化到磁盤

    1. 當(dāng)redo log buffer 的已用空間超過 innodb_log_buffer_size 規(guī)定空間一半的時(shí)候泉孩,后臺(tái)線程會(huì)主動(dòng)寫盤硼端,但是這里注意的是這個(gè)寫盤只是調(diào)用了write 沒有調(diào)用 fsync,所以說只是寫到了 page_cache 里面寓搬。
    2. 并行的事務(wù)提交的時(shí)候珍昨,順帶將這個(gè)事務(wù)的 redo log buffer 持久化到磁盤。假設(shè)一個(gè)事務(wù) A 執(zhí)行到一半句喷,已經(jīng)寫了一些 redo log 到 buffer 中镣典,這時(shí)候有另外一個(gè)線程的事務(wù) B 提交,如果 innodb_flush_log_at_trx_commit 設(shè)置的是 1唾琼,那么按照這個(gè)參數(shù)的邏輯兄春,事務(wù) B 要把 redo log buffer 里的日志全部持久化到磁盤。這時(shí)候锡溯,就會(huì)帶上事務(wù) A 在 redo log buffer 里的日志一起持久化到磁盤赶舆。

binlog

binlog 也是日志,它是server 層記錄的日志祭饭。我們來比較一下它和redo log 日志文件的不同

  1. redo log 是 引擎層產(chǎn)生的芜茵,binlog 是由server 層產(chǎn)生的,所有引擎共用
  2. redo log 它是循環(huán)寫倡蝙,binlog 是追加寫九串,它不會(huì)覆蓋數(shù)據(jù),寫完之后再換到寫一個(gè)問價(jià)寫
  3. redo log 是物理日志寺鸥,記錄的是“在某個(gè)數(shù)據(jù)頁上做了什么修改”猪钮;binlog 是邏輯日志,記錄的是這個(gè)語句的原始邏輯析既,比如“給 ID=2 這一行的 c 字段加 1 ”躬贡。

binlog 記錄有三種格式:statement、row眼坏、mixed

  1. statement 記錄的是執(zhí)行語句 主從復(fù)制時(shí)可能會(huì)出現(xiàn)問題
  2. row 記錄要修改的數(shù)據(jù) 缺點(diǎn)就是 日志文件比較大拂玻, 優(yōu)點(diǎn)就是 數(shù)據(jù)恢復(fù)
  3. mixed mysql 會(huì)根據(jù)執(zhí)行的每一條具體的 SQL 語句來區(qū)分對(duì)待記錄的日志形式,也就是在 statement 和 row 之間選擇一種

binlog 刷盤過程:

binlog 的寫入邏輯是這樣的:事務(wù)執(zhí)行過程中宰译,先把日志寫到 binlog cache檐蚜,事務(wù)提交的時(shí)候,再把 binlog cache 寫到 binlog 文件中沿侈。

一個(gè)事務(wù)的 binlog 是不能被拆開的闯第,因此不論這個(gè)事務(wù)多大,也要確保一次性寫入缀拭。系統(tǒng)給 binlog cache 分配了一片內(nèi)存咳短,每個(gè)線程一個(gè)填帽,參數(shù) binlog_cache_size 用于控制單個(gè)線程內(nèi) binlog cache 所占內(nèi)存的大小。如果超過了這個(gè)參數(shù)規(guī)定的大小咙好,就要暫存到磁盤篡腌。事務(wù)提交的時(shí)候,執(zhí)行器把 binlog cache 里的完整事務(wù)寫入到 binlog 中勾效,并清空 binlog cache嘹悼。

每個(gè)線程有自己 binlog cache,但是共用同一份 binlog 文件层宫。

binlog刷盤的時(shí)機(jī)是由參數(shù) sync_binlog 控制的

  1. sync_binlog=0 的時(shí)候杨伙,表示每次提交事務(wù)都只 write,不 fsync萌腿;

  2. sync_binlog=1 的時(shí)候限匣,表示每次提交事務(wù)都會(huì)執(zhí)行 fsync;

  3. sync_binlog=N(N>1) 的時(shí)候毁菱,表示每次提交事務(wù)都 write膛腐,但累積 N 個(gè)事務(wù)后才 fsync。因此鼎俘,在出現(xiàn) IO 瓶頸的場景里,將 sync_binlog 設(shè)置成一個(gè)比較大的值辩涝,可以提升性能贸伐。

在實(shí)際的業(yè)務(wù)場景中,考慮到丟失日志量的可控性怔揩,一般不建議將這個(gè)參數(shù)設(shè)成 0捉邢,比較常見的是將其設(shè)置為 100~1000 中的某個(gè)數(shù)值。但是商膊,將 sync_binlog 設(shè)置為 N伏伐,對(duì)應(yīng)的風(fēng)險(xiǎn)是:如果主機(jī)發(fā)生異常重啟,會(huì)丟失最近 N 個(gè)事務(wù)的 binlog 日志晕拆。

mysql 更新流程

講完了 redo log 和 binlog ,現(xiàn)在我們有了基礎(chǔ)知識(shí)藐翎,那我們現(xiàn)在就來看看更新mysql的更新過程是怎樣的?

未避免流程差異化太大实幕,這里我們?cè)O(shè)置一個(gè)前提吝镣,mysql 的版本是5.7,非自動(dòng)提交(與自動(dòng)提交差別不大 主要是為了更清楚的描述整個(gè)過程)昆庇,sync_binlog =1末贾,innodb_flush_log_at_trx_commit = 1,binlog 是打開的整吆,現(xiàn)在我們拿語句UPDATE t set c= 20 where id =2;來說明過程

  • 執(zhí)行器首先調(diào)用引擎層的接口獲得 id = 2 的數(shù)據(jù)拱撵,引擎層的內(nèi)存如果存在id = 2 這一行的頁辉川,那么直接返回給執(zhí)行器 如果不存在那么 從磁盤中加載該頁 并放在內(nèi)存中 然后再返回給server 層

  • 執(zhí)行器獲得數(shù)據(jù)后,對(duì)字段C 設(shè)置為20 拴测,并再次調(diào)用引擎層接口乓旗,引擎層先將本次要修改數(shù)據(jù)的原始數(shù)據(jù)寫到undo log 中,以防回滾昼扛,然后將本次修改記錄在 redo log 中 并存于buffer 中

  • 事務(wù)進(jìn)入提交階段(這里用到了兩階段提交)寸齐,首先將redo buffer 里面的日志寫到磁盤,并標(biāo)記為prepare狀態(tài)抄谐,并告訴執(zhí)行器 我已經(jīng)提交了渺鹦,你也可以提交了,這個(gè)時(shí)候提交的第一階段完成

  • 開始第二階段的提交蛹含,執(zhí)行器生成這個(gè)操作的binglog毅厚,并將binglog 也寫到磁盤 這個(gè)時(shí)候xid 也寫入到了binlog,binglog 落盤后浦箱,執(zhí)行器再調(diào)用引擎層的提交事務(wù)接口吸耿,將redo log 標(biāo)記為commit 狀態(tài),注意的是這個(gè)時(shí)候redo log 數(shù)據(jù)不用落盤

  • 更新完成

為什么是兩階段提交

  • 先寫 redo log 后寫 binlog

    假設(shè)在 redo log 寫完酷窥,binlog 還沒有寫完的時(shí)候咽安,MySQL 進(jìn)程異常重啟。由于我們前面說過的蓬推,redo log 寫完之后妆棒,系統(tǒng)即使崩潰,仍然能夠把數(shù)據(jù)恢復(fù)回來沸伏,所以恢復(fù)后這一行 c 的值是 1糕珊。但是由于 binlog 沒寫完就 crash 了,這時(shí)候 binlog 里面就沒有記錄這個(gè)語句毅糟。因此红选,之后備份日志的時(shí)候,存起來的 binlog 里面就沒有這條語句姆另。然后你會(huì)發(fā)現(xiàn)喇肋,如果需要用這個(gè) binlog 來恢復(fù)臨時(shí)庫的話,由于這個(gè)語句的 binlog 丟失蜕青,這個(gè)臨時(shí)庫就會(huì)少了這一次更新苟蹈,恢復(fù)出來的這一行 c 的值就是 0,與原庫的值不同右核。

  • 先寫 binlog 后寫 redo log慧脱。

    如果在 binlog 寫完之后 crash,由于 redo log 還沒寫贺喝,崩潰恢復(fù)以后這個(gè)事務(wù)無效菱鸥,所以這一行 c 的值是 0宗兼。但是 binlog 里面已經(jīng)記錄了“把 c 從 0 改成 1”這個(gè)日志。所以氮采,在之后用 binlog 來恢復(fù)的時(shí)候就多了一個(gè)事務(wù)出來殷绍,恢復(fù)出來的這一行 c 的值就是 1,與原庫的值不同鹊漠。

系統(tǒng)重啟主到,數(shù)據(jù)恢復(fù)過程

數(shù)據(jù)庫恢復(fù)后會(huì)判斷redo log的事務(wù)是不是完整的,如果不是則根據(jù)undo log回滾躯概;如果是完整的并且是prepare狀態(tài)登钥,則進(jìn)一步判斷對(duì)應(yīng)的事務(wù)binlog是不是完整的,如果不完整則一樣根據(jù)undo log進(jìn)行回娶靡,如果是binlog是完整的就進(jìn)行提交

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牧牢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子姿锭,更是在濱河造成了極大的恐慌塔鳍,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呻此,死亡現(xiàn)場離奇詭異轮纫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)焚鲜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門蜡感,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恃泪,你說我怎么就攤上這事∠” “怎么了贝乎?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長叽粹。 經(jīng)常有香客問我览效,道長,這世上最難降的妖魔是什么虫几? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任锤灿,我火速辦了婚禮,結(jié)果婚禮上辆脸,老公的妹妹穿的比我還像新娘但校。我一直安慰自己,他們只是感情好啡氢,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布状囱。 她就那樣靜靜地躺著术裸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亭枷。 梳的紋絲不亂的頭發(fā)上袭艺,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音叨粘,去河邊找鬼猾编。 笑死,一個(gè)胖子當(dāng)著我的面吹牛升敲,可吹牛的內(nèi)容都是我干的答倡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼冻晤,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼苇羡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鼻弧,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤设江,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后攘轩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叉存,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年度帮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了歼捏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡笨篷,死狀恐怖瞳秽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情率翅,我是刑警寧澤练俐,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站冕臭,受9級(jí)特大地震影響腺晾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辜贵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一悯蝉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧托慨,春花似錦鼻由、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跺撼。三九已至,卻和暖如春讨彼,著一層夾襖步出監(jiān)牢的瞬間歉井,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工哈误, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哩至,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓蜜自,卻偏偏與公主長得像菩貌,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子重荠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容