MySQL InnoDB如何保證事務(wù)特性

如果有人問你“數(shù)據(jù)庫事務(wù)有哪些特性”瑞侮?你可能會很快回答出原子性、一致性溪食、隔離性囊卜、持久性即ACID特性。那么你知道InnoDB如何保證這些事務(wù)特性的嗎错沃?如果知道的話這篇文章就可以直接跳過不看啦(#.#)

先說結(jié)論:

  • redo log重做日志用來保證事務(wù)的持久性
  • undo log回滾日志保證事務(wù)的原子性
  • undo log+redo log保證事務(wù)的一致性
  • 鎖(共享栅组、排他)用來保證事務(wù)的隔離性

重做日志 redo log

重做日志 redo log 分為兩部分:一部分是內(nèi)存中的重做日志緩沖(redo log buffer),是易丟失的枢析;二部分是重做日志文件(redo log file)玉掸,是持久的。InnoDB通過Force Log at Commit機(jī)制來實現(xiàn)持久性醒叁,當(dāng)commit時司浪,必須先將事務(wù)的所有日志寫到重做日志文件進(jìn)行持久化泊业,待commit操作完成才算完成。
InnoDB在下面情況下會將重做日志緩沖的內(nèi)容寫入重做日志文件:

  • master thread 每一秒將重做日志緩沖刷新到重做日志文件;
  • 每個事務(wù)提交時
  • 當(dāng)重做日志緩沖池剩余空間小于1/2時

為了確保每次日志都寫入重做日志文件啊易,在每次將日志緩沖寫入重做日志文件后吁伺,InnoDB存儲引擎都需要調(diào)用一次fsync(刷盤)操作。但這也不是絕對的租谈。用戶可以通過修改innodb_flush_log_at_trx_commoit參數(shù)來控制重做日志刷新到磁盤的策略篮奄,這個可以作為大量事務(wù)提交時的優(yōu)化點。

  • 1參數(shù)默認(rèn)值割去,表示事務(wù)提交時必須調(diào)用一次fsync操作窟却。
  • 0表示事務(wù)提交時,重做日志緩存并不立即寫入重做日志文件呻逆,而是隨著Master Thread的間隔進(jìn)行fsync操作夸赫。
  • 2表示事務(wù)提交時將重做日志寫入重做日志文件,但僅寫入文件系統(tǒng)的緩存中页慷,不進(jìn)行fsync操作憔足。
    fsync的效率取決于磁盤的性能,因此磁盤的性能決定了事務(wù)提交的性能酒繁,也就是數(shù)據(jù)庫的性能滓彰。所以如果有人問你如何優(yōu)化Mysql數(shù)據(jù)庫的時候別忘了有硬件這一條,讓他們提升硬盤配置州袒,換SSD固態(tài)硬盤
    重做日志都是以512字節(jié)進(jìn)行存儲的揭绑,稱之為重做日志塊,與磁盤扇區(qū)大小一致郎哭,這意味著重做日志的寫入可以保證原子性他匪,不需要doublewrite技術(shù)。它有以下3個特性:
  • 重做日志是在InnoDB層產(chǎn)生的
  • 重做日志是物理格式日志夸研,記錄的是對每個頁的修改
  • 重做日志在事務(wù)進(jìn)行中不斷被寫入邦蜜,而且是順序?qū)懭?/li>

MySQL的innoDB存儲引擎,使用Redo log保證了事務(wù)的持久性亥至。當(dāng)事務(wù)提交時悼沈,必須先將事務(wù)的所有日志寫入日志文件進(jìn)行持久化,就是我們常說的WAL(write ahead log)機(jī)制姐扮。這樣才能保證斷電或宕機(jī)等情況發(fā)生后絮供,已提交的事務(wù)不會丟失,這個能力稱為 crash-safe茶敏。

Redo log包括兩部分壤靶,重做日志緩沖(redo log buffer)和重做日志文件(redo log file),前者是易失的緩存惊搏,后者是持久化的文件贮乳。

舉一個事務(wù)的例子:

步驟1:begin;

步驟2:insert into t1 …r

步驟3:insert into t2 …

步驟4:commit;

這個事務(wù)的寫入過程實際拆解如下:

圖片.png

重點關(guān)注在這個事務(wù)提交前忧换,將redo log的寫入拆成了兩個步驟,prepare 和 commit塘揣,這就是"兩階段提交”包雀。那么為什么要采用兩階段提交呢宿崭?

實際上亲铡,兩階段提交是分布式系統(tǒng)常用的機(jī)制。MySQL使用了兩階段提交后葡兑,也是為了保證事務(wù)的持久性奖蔓。Redo log 和bingo 有一個共同的數(shù)據(jù)字段,叫 XID,崩潰恢復(fù)的時候讹堤,會按順序掃描 redo log吆鹤。

假設(shè)在寫入binlog前系統(tǒng)崩潰,那么數(shù)據(jù)庫恢復(fù)后順序掃描 redo log洲守,碰到只有 parepare疑务、而沒有 commit 的 redo log,就拿著 XID 去 binlog 找對應(yīng)的事務(wù)梗醇,而且binlog也沒寫入知允,所以事務(wù)就直接回滾了。

假設(shè)在寫入binlog之后叙谨,事務(wù)提交前數(shù)據(jù)庫崩潰温鸽,那么數(shù)據(jù)庫恢復(fù)后順序掃描 redo log,碰到既有 prepare手负、又有 commit 的 redo log涤垫,就直接提交,保證數(shù)據(jù)不丟失竟终。

這個事務(wù)要往兩個表中插入記錄蝠猬,插入數(shù)據(jù)的過程中,生成的日志都得先寫入redo log buffer 统捶,等到commit的時候榆芦,才真正把日志寫到 redo log 文件。(當(dāng)然瘾境,這里不絕對歧杏,因為redo log buffer可能因為其他原因被迫刷新到redo log)。

而為了確保每次日志都能寫入日志文件迷守,在每次將重做日志緩沖寫入重做日志文件后犬绒,InnoDB存儲引擎都需要調(diào)用一次fsync操作,確保寫入了磁盤兑凿。

對于redo log的持久化凯力,可以如下圖所示茵瘾。


圖片.png

1)先寫入redo log buffer,在藍(lán)色區(qū)域咐鹤。

2)寫入redo log file拗秘,但是還沒有fsync,這時候是處于黃色的位置祈惶,處于系統(tǒng)緩存雕旨。

3)調(diào)用fsync,真正寫入磁盤捧请。

為了控制 redo log 的寫入策略凡涩,InnoDB 提供了 innodb_flush_log_at_trx_commit 參數(shù),它有三種可能取值:

設(shè)置為 0 的時候疹蛉,表示每次事務(wù)提交時都只是把 redo log 留在 redo log buffer 中 ;

設(shè)置為 1 的時候活箕,表示每次事務(wù)提交時都將 redo log 直接持久化到磁盤;

設(shè)置為 2 的時候可款,表示每次事務(wù)提交時都只是把 redo log 寫到 page cache育韩。

binlog的寫入和redo log一樣,也是包括bingo cache和bingo file闺鲸,同樣跟上面的三色層次類似(當(dāng)然筋讨,binlog是server層的,不是存儲引擎層的)翠拣,包括log buffer版仔、文件系統(tǒng)page cache、hard disk误墓。

寫入page cache 和 fsync到disk 的時機(jī)蛮粮,是由參數(shù) sync_binlog 控制的:

sync_binlog=0 的時候,表示每次提交事務(wù)都只 寫入文件系統(tǒng)的page cache谜慌,不 fsync然想;

sync_binlog=1 的時候,表示每次提交事務(wù)都會執(zhí)行 fsync欣范;

sync_binlog=N(N>1) 的時候变泄,表示每次提交事務(wù)都寫入文件系統(tǒng)的page cache,但累積 N 個事務(wù)后才 fsync恼琼。(如果主機(jī)發(fā)生異常重啟妨蛹,會丟失最近 N 個事務(wù)的 binlog 日志)

通常我們說MySQL的“雙 1”配置,指的就是sync_binlog和 innodb_flush_log_at_trx_commit 都設(shè)置成 1晴竞。也就是說蛙卤,一個事務(wù)完整提交前,需要等待兩次刷盤,一次是 redo log(prepare 階段)颤难,一次是 binlog神年。

回滾日志 undo log

為了滿足事務(wù)的原子性,在操作任何數(shù)據(jù)之前行嗤,首先將數(shù)據(jù)備份到一個地方(這個存儲數(shù)據(jù)備份的地方稱為Undo Log)已日,然后進(jìn)行數(shù)據(jù)的修改。如果出現(xiàn)了錯誤或者用戶執(zhí)行了 ROLLBACK語句栅屏,系統(tǒng)可以利用Undo Log中的備份將數(shù)據(jù)恢復(fù)到事務(wù)開始之前的狀態(tài)飘千。
undo log實現(xiàn)多版本并發(fā)控制(MVCC)來輔助保證事務(wù)的隔離性。

回滾日志不同于重做日志既琴,它是邏輯日志占婉,對數(shù)據(jù)庫的修改都邏輯的取消了泡嘴。當(dāng)事務(wù)回滾時甫恩,它實際上做的是與先前相反的工作。對于每個INSERT酌予,InnoDB存儲引擎都會完成一個DELETE磺箕;對于每個UPDATE,InnoDB存儲引擎都會執(zhí)行一個相反的UPDATE抛虫。

事務(wù)提交后并不能馬上刪除undo log松靡,這是因為可能還有其他事務(wù)需要通過undo log 來得到行記錄之前的版本。故事務(wù)提交時將undo log 放入一個鏈表中建椰,是否可以刪除undo log 根據(jù)操作不同分以下2種情況:

  • Insert undo log: insert操作的記錄雕欺,只對事務(wù)本身可見,對其他事務(wù)不可見(這是事務(wù)隔離性的要求),故該undo log可以在事務(wù)提交后直接刪除棉姐。不需要進(jìn)行 purge操作屠列。
  • update undo log:記錄的是對 delete和 update操作產(chǎn)生的 undo log。該undo log可能需要提供MVCC機(jī)制伞矩,因此不能在事務(wù)提交時就進(jìn)行刪除笛洛。提交時放入undo log鏈表,等待 purge線程進(jìn)行最后的刪除。

事務(wù)的隔離性的實現(xiàn)原理就是鎖乃坤,因而隔離性也可以稱為并發(fā)控制苛让、鎖等。事務(wù)的隔離性要求每個讀寫事務(wù)的對象對其他事務(wù)的操作對象能互相分離湿诊。再者狱杰,比如操作緩沖池中的LRU列表,刪除厅须,添加仿畸、移動LRU列表中的元素,為了保證一致性那么就要鎖的介入九杂。

鎖的類型

InnoDB主要有2種鎖:行級鎖颁湖,意向鎖

行級鎖:

  • 共享鎖(讀鎖 S)宣蠕,允許事務(wù)讀一行數(shù)據(jù)。事務(wù)拿到某一行記錄的共享S鎖甥捺,才可以讀取這一行抢蚀,并阻止別的事務(wù)對其添加X鎖。共享鎖的目的是提高讀讀并發(fā)镰禾。
  • 排它鎖(寫鎖 X)皿曲,允許事務(wù)刪除一行數(shù)據(jù)或者更新一行數(shù)據(jù)。事務(wù)拿到某一行記錄的排它X鎖吴侦,才可以修改或者刪除這一行屋休。排他鎖的目的是為了保證數(shù)據(jù)的一致性。

行級鎖中备韧,除了S和S兼容劫樟,其他都不兼容。

意向鎖:

  • 意向共享鎖(讀鎖 IS )织堂,事務(wù)想要獲取一張表的幾行數(shù)據(jù)的共享鎖叠艳,事務(wù)在給一個數(shù)據(jù)行加共享鎖前必須先取得該表的IS鎖。
  • 意向排他鎖(寫鎖 IX)易阳,事務(wù)想要獲取一張表中幾行數(shù)據(jù)的排它鎖附较,事務(wù)在給一個數(shù)據(jù)行加排他鎖前必須先取得該表的IX鎖。
    解釋一下意向鎖
The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.

意向鎖的主要用途是為了表達(dá)某個事務(wù)正在鎖定一行或者將要鎖定一行數(shù)據(jù)潦俺。e.g:事務(wù)A要對一行記錄r進(jìn)行上X鎖拒课,那么InnoDB會先申請表的IX鎖,再鎖定記錄r的X鎖事示。在事務(wù)A完成之前早像,事務(wù)B想要來個全表操作,此時直接在表級別的IX就告訴事務(wù)B需要等待而不需要在表上判斷每一行是否有鎖很魂。意向排它鎖存在的價值在于節(jié)約InnoDB對于鎖的定位和處理性能扎酷。另外注意了,除了全表掃描以外意向鎖都不會阻塞遏匆。

鎖的算法

InnoDB有三種行鎖的算法:

  • Record Lock:單個行記錄上的鎖
  • Gap Lock:間隙鎖法挨,鎖定一個范圍,而非記錄本身
  • Next-Key Lock:結(jié)合Gap Lock和Record Lock幅聘,鎖定一個范圍凡纳,并且鎖定記錄本身。主要解決的問題是REPEATABLE READ隔離級別下的幻讀帝蒿〖雒樱可以參考文章了解事務(wù)隔離級別的相關(guān)知識點。

這里主要講一下Next-Key Lock,利用Next-key Lock鎖定的不是單個值而是一個范圍暴氏,他的目的就是為了阻止多個事務(wù)將記錄插入到同一范圍內(nèi)從而導(dǎo)致幻讀延塑。

注意了,如果走唯一索引答渔,那么Next-Key Lock會降級為Record Lock关带,即僅鎖住索引本身,而不是范圍沼撕。也就是說Next-Key Lock前置條件為事務(wù)隔離級別為RR且查詢的索引走的非唯一索引宋雏、主鍵索引。

下面我們用個例子詳細(xì)說一下务豺。
首先建立一張表:

sql

CREATE TABLE T (id int ,f_id int,PRIMARY KEY (id), KEY(f_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8
insert into T SELECT 1,1;
insert into T SELECT 3,1;
insert into T SELECT 5,3;
insert into T SELECT 7,6;
insert into T SELECT 10,8;

事務(wù)A執(zhí)行如下語句:

sql

SELECT * FROM T WHERE f_id = 3 FOR UPDATE

這時SQL語句走非唯一索引磨总,因此使用Next-Key Locking加鎖,并且有2個索引笼沥,其需要分別進(jìn)行鎖定蚪燕。
對于聚集索引,其僅對id等于5的索引加上Record Lock敬拓。而對于輔助索引邻薯,其加上Next-Key Lock,鎖定了范圍(1,3)乘凸,特別需要注意的是,InnoDB存儲引擎還會對輔助索引下一個鍵值加上Gap Lock累榜,即范圍(3.6)的鎖营勤。
所以如果在新session中執(zhí)行如下語句都會報錯[Err] 1205 - Lock wait timeout exceeded; try restarting transaction

sql

select * from T where id = 5 lock in share MODE -- 不能執(zhí)行,因為事務(wù)A已經(jīng)給id=5的值加上了X鎖壹罚,執(zhí)行會被阻塞
INSERT INTO T SELECT 4,2  -- 不能執(zhí)行葛作,輔助索引的值為2,在(1,3)的范圍內(nèi)猖凛,執(zhí)行阻塞
INSERT INTO T SELECT 6,5  -- 不能執(zhí)行赂蠢,gap鎖會鎖住(3,6)的范圍,執(zhí)行阻塞

此時想象一下,事務(wù)A鎖定了f_id =5 的記錄绿渣, 正常會有個gap lock谬俄,鎖住(5,6)劣摇,那么如果沒有(5,6)的gap鎖,那么用戶可以插入索引 f_id 為5的記錄,這樣事務(wù)A再次查詢就會返回一個不同的記錄蔑滓,也就導(dǎo)致了幻讀的產(chǎn)生。

同理,如果我們事務(wù)A執(zhí)行的是select * from T where f_id = 10 FOR UPDATE,在表里查不到數(shù)據(jù)键袱,但是基于Next-Key Lock會鎖琢蔷健(8,+∞)蹄咖,我們執(zhí)行INSERT INTO T SELECT 6,11是無法插入成功的荠耽,這就從根本上解決了幻讀問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末比藻,一起剝皮案震驚了整個濱河市铝量,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌银亲,老刑警劉巖慢叨,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異务蝠,居然都是意外死亡拍谐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門馏段,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轩拨,“玉大人,你說我怎么就攤上這事院喜⊥鋈兀” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵喷舀,是天一觀的道長砍濒。 經(jīng)常有香客問我,道長硫麻,這世上最難降的妖魔是什么爸邢? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮拿愧,結(jié)果婚禮上杠河,老公的妹妹穿的比我還像新娘。我一直安慰自己浇辜,他們只是感情好券敌,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著奢赂,像睡著了一般陪白。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上膳灶,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天咱士,我揣著相機(jī)與錄音立由,去河邊找鬼。 笑死序厉,一個胖子當(dāng)著我的面吹牛锐膜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弛房,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼道盏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了文捶?” 一聲冷哼從身側(cè)響起荷逞,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎粹排,沒想到半個月后种远,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡顽耳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年坠敷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片射富。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡膝迎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胰耗,到底是詐尸還是另有隱情限次,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布宪郊,位于F島的核電站掂恕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏弛槐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一依啰、第九天 我趴在偏房一處隱蔽的房頂上張望乎串。 院中可真熱鬧,春花似錦速警、人聲如沸叹誉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽长豁。三九已至,卻和暖如春忙灼,著一層夾襖步出監(jiān)牢的瞬間匠襟,已是汗流浹背钝侠。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留酸舍,地道東北人帅韧。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像啃勉,于是被迫代替她去往敵國和親忽舟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

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