一文了解InnoDB事務實現(xiàn)原理

本文準備通俗的講解MySQL的InnoDB存儲引擎事務的實現(xiàn)原理。

首先,我們知道事務具有ACID四個特性盗蟆。也即:原子性,一致性鉴未,隔離性,持久性。

這四個性質(zhì)我們不用干癟的文字去闡述,我們只需要知道事務保證了一系列的操作要么全部執(zhí)行殃姓,要么一個也不執(zhí)行,同時一旦事務提交瓦阐,則其所做的修改會永久保存到數(shù)據(jù)庫即可。

接下來我們一起看看InnoDB怎么實現(xiàn)的事務篷牌。

ACD三個特性是通過Redo log(重做日志)和Undo log 實現(xiàn)的睡蟋。 而隔離性是通過鎖來實現(xiàn)的。由于隔離性和鎖在之前的文章講過了枷颊。所以本文重點關注Redo log 和Undo log戳杀。

一、Redo log

重做日志用來實現(xiàn)事務的持久性夭苗,即D特性信卡。它由兩部分組成:

①內(nèi)存中的重做日志緩沖
②重做日志文件

一看有內(nèi)存和磁盤上的兩個對應實體,我們就知道這樣做一定是為了效率考慮题造,因為內(nèi)存的讀寫效率要比磁盤讀寫效率高太多傍菇。

Innodb是支持事務的存儲引擎,在事務提交時界赔,必須先將該事務的所有日志寫入到redo日志文件中丢习,待事務的commit操作完成才算整個事務操作完成。在每次將redo log buffer寫入redo log file后淮悼,都需要調(diào)用一次fsync操作咐低,因為重做日志緩沖只是把內(nèi)容先寫入操作系統(tǒng)的緩沖系統(tǒng)中,并沒有確保直接寫入到磁盤上袜腥,所以必須進行一次fsync操作见擦。因此,磁盤的性能在一定程度上也決定了事務提交的性能。

關于fsync這個操作用戶是可以干預的鲤屡,因為每次提交事務都執(zhí)行一次fsync儡湾,確實影響數(shù)據(jù)庫性能。通過innodb_flush_log_at_trx_commit來控制redo log刷新到磁盤的策略执俩。該參數(shù)的默認值為1徐钠,表示每次提交事務時都執(zhí)行一次fsync操作。0則表示事務提交時不進行寫入重做日志文件役首,這個寫入操作由master thread進程來完成尝丐,master thread每一秒會進行一次重做日志文件的fsync操作。2則表示事務提交時將重做日志寫入重做日志文件衡奥,但僅寫入文件系統(tǒng)的緩存中爹袁,并不進行fsync操作。用戶可以通過設置0或者2啦提高事務提交的性能矮固,也可以設置1來要求確保redo log是寫入文件中的失息,總之三種方法各有利弊。

還有需要了解的是:
redo log buffer將內(nèi)存中的log block刷新到磁盤是有一定的規(guī)則的:事務提交時(前面已經(jīng)提到)档址、當log buffer中有一半的內(nèi)存空間被使用時盹兢、log checkpoint時。

那接下來我們就需要看看redo log file存儲的內(nèi)容到底是什么了守伸。

為了避免大家懵圈绎秒,不打算把存儲格式一個一個細鉆(我也沒那實力,哈哈)尼摹。我們只需要知道他大致是怎么設計的就行了见芹。這樣,我們以后如果自己設計一個類似場景的產(chǎn)品蠢涝,就完全可以借鑒它的設計思想啦玄呛。

好,開始:
在InnoDB存儲引擎中和二,重做日志都是以512字節(jié)為單位進行存儲的徘铝,這意味著重做日志緩存、重做日志文件塊都是以塊(block)的方式進行保存的儿咱,稱為重做日志塊(redo log block)庭砍。每塊的大小512字節(jié)。由于重做日志塊的大小和磁盤扇區(qū)大小一樣混埠,都是512字節(jié)怠缸,因此重做日志的寫入可以保證原子性,不需要double write技術钳宪。

每個重做日志塊的內(nèi)容快除了日志記錄本身之外揭北,還由日志塊頭(log block header)及日志塊尾(log block tailer)兩部分組成扳炬。重做日志頭一共占用12字節(jié),重做日志尾占用8字節(jié)搔体。這兩部分是固定的恨樟。故每個重做日志塊實際可以存儲的大小為492字節(jié)(512-12-8),如下圖顯示重做日志塊緩存的結構:

在圖中標注出來不用太過關注這幾個字段的含義疚俱,因為他們對理解Redo log實現(xiàn)事務的機制沒有太大影響劝术,反而如果關注這些,容易讓人看到這些大寫字母的變量感到頭暈呆奕。

ps:這些變量是維護log block狀態(tài)的一些變量养晋。比如表示log block當前使用量,當前redo block的第一個redo log開始位置等等梁钾。舉個例子吧:

事務T1的重做日志1占用762字節(jié)绳泉,事務T2的重做日志占用100字節(jié),姆泻。由于每個log block實際只能保存492字節(jié)零酪,因此其在log buffer的情況應該如下圖所示:

實現(xiàn)這個功能就是靠log block的頭部的字段來實現(xiàn)的。好了拇勃,這不是我們關注的問題四苇,講這個只是為了滿足大家的好奇心以及對這些變量的初步認識。

重做日志塊中出去header和tailer的內(nèi)容就是具體的redo log了潜秋。不同的數(shù)據(jù)庫操作會有對應的重做日志格式蛔琅。此外,由于InnoDB存儲引擎的存儲管理是基于頁的峻呛,故其重做日志格式也是基于頁的。雖然有著不同的重做日志格式辜窑,但他們有著通用的頭部格式钩述,如圖:


通用的頭部格式由一下3部分組成
redo_log_type: 重做日志類型
space:表空間ID
page_no 頁的偏移量即頁的位置

之后是redo log body ,根據(jù)重做日志類型的不同穆碎,會有不同的存儲內(nèi)容牙勘,例如,對于頁上記錄的插入和刪除操作所禀,分別對應的如圖的格式(同樣方面,不要細扣每一個字段的含義,這不是我們要抓的重點):

大體上的redo log結構介紹完了色徘。在說從redo log file恢復之前恭金,還要說一個LSN的概念,LSN是Log Sequence Number的縮寫褂策,其代表的是日志序列號横腿,在InnoDB存儲引擎中颓屑,LSN占用8個字節(jié),并且單調(diào)遞增耿焊。

LSN表示事務寫入重做日志字節(jié)的總量揪惦。例如當前重做日志的LSN為1000,有一個事務T1寫入了100字節(jié)的重做日志罗侯,那么LSN就變成1100器腋,若又有事務T2寫入200字節(jié)的重做日志,那么LSN就變?yōu)?300钩杰。

LSN不僅記錄在重做日志中纫塌,還存在每個頁中,在每個頁的頭部榜苫,有一個值FIL_PAGE_LSN护戳,記錄了該頁的LSN,在頁中垂睬,LSN表示該頁最后刷新時LSN的大小媳荒。因為重做日志記錄的是每個頁的日志,因此頁中的LSN可以判斷頁是否需要進行恢復操作驹饺。例如钳枕,頁P1的LSN為10000,而數(shù)據(jù)庫啟動時赏壹,InnoDB檢測到寫入重做日志中的LSN為13000鱼炒,并且事務已經(jīng)提交,那么數(shù)據(jù)庫需要進行恢復操作蝌借。將重做日志應用到P1頁中昔瞧,同樣的,對于重做日志中LSN小于P1頁的LSN菩佑,不需要進行重做自晰,因為P1頁中的LSN表示已經(jīng)被刷新到該位置,在此位置之前的內(nèi)容已經(jīng)被成功的處理了稍坯。

接下來就是恢復操作了:
InnoDB存儲引擎在啟動時不管上次數(shù)據(jù)運行是否正常關閉酬荞,都會嘗試進行恢復操作,因為重做日志記錄的是物理日志(不要糾結這個)瞧哟,因此恢復的速度比邏輯日志混巧,如二進制日志要快的多,于此同時勤揩,InnoDB存儲引擎自身也對恢復進行了一定程度的優(yōu)化咧党,如順序讀取及并行應用重做日志,這樣可以進一步提高數(shù)據(jù)庫恢復的速度

由于checkpoint表示已經(jīng)刷新到磁盤頁上的LSN雄可,因此在恢復過程中僅需恢復checkpoint開始的日志部分凿傅。對于圖中的例子缠犀,當數(shù)據(jù)庫在checkpoint的LSN為10 000時發(fā)生宕機,恢復操作僅恢復LSN 10000~13000范圍內(nèi)的日志聪舒。

物理日志
舉個例子辨液,對于Insert操作,物理日志記錄的是每個頁的變化:
若執(zhí)行SQL語句:
INSERT INTO t SELECT 1,2;
其記錄的重做日志大致類似這個樣子:
page(2,3),offset 32,value 1,2

二箱残、Undo log

第二部分是Undo log滔迈,它可以實現(xiàn)如下兩個功能:
1.實現(xiàn)事務回滾
2.實現(xiàn)MVCC

undo log和redo log記錄物理日志不一樣,它是邏輯日志被辑×呛罚可以認為當delete一條記錄時,undo log中會記錄一條對應的insert記錄盼理,反之亦然谈山,當update一條記錄時,它記錄一條對應相反的update記錄宏怔。

當執(zhí)行回滾時奏路,就可以從undo log中的邏輯記錄讀取到相應的內(nèi)容并進行回滾。有時候應用到行版本控制的時候臊诊,也是通過undo log來實現(xiàn)的:當讀取的某一行被其他事務鎖定時鸽粉,它可以從undo log中分析出該行記錄以前的數(shù)據(jù)是什么,從而提供該行版本信息抓艳,幫助用戶實現(xiàn)一致性非鎖定讀取触机。我們舉一個具體的例子。例子來自此文玷或。

這個例子主要演示事務對某行記錄的更新過程:

在演示之前儡首,補充一下:
InnoDB為每行記錄都實現(xiàn)了三個隱藏字段,用來實現(xiàn)MVCC:

  • 6字節(jié)的事務ID(DB_TRX_ID ,每處理一個事務偏友,其值自動+1椒舵。
  • 7字節(jié)的回滾指針(DB_ROLL_PTR),指向?qū)懙絩ollback segment(回滾段)的一條undo log記錄约谈。
  • 隱藏的ID

1. 初始數(shù)據(jù)行

F1~F6是某行列的名字,1~6是其對應的數(shù)據(jù)犁钟。后面三個隱含字段分別對應該行的事務號和回滾指針棱诱,假如這條數(shù)據(jù)是剛INSERT的,可以認為ID為1涝动,其他兩個字段為空迈勋。

2.事務1更改該行的各字段的值

當事務1更改該行的值時,會進行如下操作:

  • 用排他鎖鎖定該行
  • 記錄redo log
  • 把該行修改前的值Copy(可以理解成Copy醋粟,不要糾結前面說反向更新這里說復制靡菇,原理是一樣的)到undo log重归,即上圖中下面的行
  • 修改當前行的值,填寫事務編號厦凤,使回滾指針指向undo log中的修改前的行鼻吮。

3.事務2修改該行的值

與事務1相同,此時undo log较鼓,中有有兩行記錄椎木,并且通過回滾指針連在一起。

這些通過回滾指針聯(lián)系起來的行相當于是數(shù)據(jù)的多個快照博烂,從而實現(xiàn)MVCC的一致性非鎖定讀了香椎。

具體規(guī)則如下:

InnoDB的MVCC,是通過上面我們說的每行紀錄后面隱藏的列來實現(xiàn)的禽篱。他們保存了行的創(chuàng)建時間和行的過期時間(或刪除時間)畜伐,當然存儲的并不是實際的時間值,而是系統(tǒng)版本號躺率。每開始一個新的事務玛界,系統(tǒng)版本號都會自動遞增。事務開始時刻的系統(tǒng)版本號會作為事務的版本號肥照,用來和查詢到的每行紀錄的版本號進行比較脚仔。在REPEATABLE READ隔離級別下,MVCC具體的操作如下:

SELECT
InnoDB會根據(jù)以下兩個條件檢查每行紀錄:

  • InnoDB只查找版本早于當前事務版本的數(shù)據(jù)行舆绎,即鲤脏,行的系統(tǒng)版本號小于或等于事務的系統(tǒng)版本號,這樣可以確保事務讀取的行吕朵,要么是在事務開始前已經(jīng)存在的猎醇,要么是事務自身插入或者修改過的。
  • 行的刪除版本努溃,要么未定義硫嘶,要么大于當前事務版本號。這樣可以確保事務讀取到的行梧税,在事務開始之前未被刪除沦疾。
    只有符合上述兩個條件的紀錄,才能作為查詢結果返回第队。

INSERT

  • InnoDB為插入的每一行保存當前系統(tǒng)版本號作為行版本號哮塞。

DELETE

  • InnoDB為刪除的每一行保存當前系統(tǒng)版本號作為行刪除標識。

UPDATE

  • InnoDB為插入一行新紀錄凳谦,保存當前系統(tǒng)版本號作為行版本號忆畅,同時,保存當前系統(tǒng)版本號到原來的行作為行刪除標識尸执。

讀到這里家凯,也許會有一個疑問缓醋,考慮如下執(zhí)行序列:


011.png

按照之前的Select規(guī)則,會話B 的事務是在 會話A的后面開啟的绊诲,那么B的事務版本號大于A的事務版本號送粱。這樣在A中插入的數(shù)據(jù)在未提交的情況下,B可以讀到A修改的數(shù)據(jù)驯镊,這不就自相矛盾了么葫督?

其實不是,InnoDB通過read view來確定一致性讀時的數(shù)據(jù)庫snapshot,InnoDB的read view確定一條記錄能否看到,有兩條法則 :
1 看不到read view創(chuàng)建時刻以后啟動的事務
2 看不到read view創(chuàng)建時活躍的事務

對于Session A板惑,start transaction時并沒有創(chuàng)建read view橄镜,而是在update語句才創(chuàng)建。所以Session A 的read view創(chuàng)建時間要比Session B的晚冯乘。所以B是不會看到A的操作的洽胶。因此防止了不可重復讀。

兩條法則原文描述如下:
Rule 1: When the read view object is created it notes down the smallest transaction identifier that is not yet used as a transaction identifier (trx_sys_t::max_trx_id). The read view calls it the low limit. So the transaction using the read view must not see any transaction with identifier greater than or equal to this low limit.

Rule 2: The transaction using the read view must not see a transaction that was active when the read view was created.

補充:如果undo log一直不刪除裆馒,則會通過當前記錄的回滾指針回溯到該行創(chuàng)建時的初始內(nèi)容姊氓,所幸的時在Innodb中存在purge線程,它會查詢那些比現(xiàn)在最老的活動事務還早的undo log喷好,并刪除它們翔横,從而保證undo log文件不至于無限增長。

關注公眾號: “Java不睡覺”梗搅, 回復:“資源”禾唁。獲取大數(shù)據(jù)全套視頻和大量Java書籍

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市无切,隨后出現(xiàn)的幾起案子荡短,更是在濱河造成了極大的恐慌,老刑警劉巖哆键,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掘托,死亡現(xiàn)場離奇詭異,居然都是意外死亡籍嘹,警方通過查閱死者的電腦和手機闪盔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辱士,“玉大人锭沟,你說我怎么就攤上這事∈恫梗” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵辫红,是天一觀的道長凭涂。 經(jīng)常有香客問我祝辣,道長,這世上最難降的妖魔是什么切油? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任蝙斜,我火速辦了婚禮,結果婚禮上澎胡,老公的妹妹穿的比我還像新娘孕荠。我一直安慰自己,他們只是感情好攻谁,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布稚伍。 她就那樣靜靜地躺著,像睡著了一般戚宦。 火紅的嫁衣襯著肌膚如雪个曙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天受楼,我揣著相機與錄音垦搬,去河邊找鬼。 笑死艳汽,一個胖子當著我的面吹牛猴贰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播河狐,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼米绕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了甚牲?” 一聲冷哼從身側(cè)響起义郑,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丈钙,沒想到半個月后非驮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡雏赦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年劫笙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片星岗。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡填大,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俏橘,到底是詐尸還是另有隱情允华,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站靴寂,受9級特大地震影響磷蜀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜百炬,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一褐隆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剖踊,春花似錦庶弃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至圃验,卻和暖如春掉伏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背澳窑。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工斧散, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人摊聋。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓鸡捐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親麻裁。 傳聞我的和親對象是個殘疾皇子箍镜,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

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