在關(guān)系型數(shù)據(jù)庫(kù)中搀捷,事務(wù)的重要性不言而喻奉芦,只要對(duì)數(shù)據(jù)庫(kù)稍有了解的人都知道事務(wù)具有 ACID 四個(gè)基本屬性,而我們不知道的可能就是數(shù)據(jù)庫(kù)是如何實(shí)現(xiàn)這四個(gè)屬性的骗奖;在這篇文章中确徙,我們將對(duì)事務(wù)的實(shí)現(xiàn)進(jìn)行分析,嘗試?yán)斫鈹?shù)據(jù)庫(kù)是如何實(shí)現(xiàn)事務(wù)的执桌,當(dāng)然我們也會(huì)在文章中簡(jiǎn)單對(duì) MySQL 中對(duì) ACID 的實(shí)現(xiàn)進(jìn)行簡(jiǎn)單的介紹鄙皇。
原子性(atomicity)
一致性(consistency)
隔離性(isolation)
持久性(durability)
事務(wù)其實(shí)就是并發(fā)控制的基本單位;相信我們都知道仰挣,事務(wù)是一個(gè)序列操作伴逸,其中的操作要么都執(zhí)行,要么都不執(zhí)行膘壶,它是一個(gè)不可分割的工作單位错蝴;接下來我們將依次介紹數(shù)據(jù)庫(kù)是如何實(shí)現(xiàn)這四個(gè)特性的。
事務(wù)的特性
原子性
在學(xué)習(xí)事務(wù)時(shí)颓芭,經(jīng)常有人會(huì)告訴你顷锰,事務(wù)就是一系列的操作,要么全部都執(zhí)行亡问,要都不執(zhí)行官紫,這其實(shí)就是對(duì)事務(wù)原子性的刻畫;雖然事務(wù)具有原子性玛界,但是原子性并不是只與事務(wù)有關(guān)系万矾,它的身影在很多地方都會(huì)出現(xiàn)。
由于操作并不具有原子性慎框,并且可以再分為多個(gè)操作,當(dāng)這些操作出現(xiàn)錯(cuò)誤或拋出異常時(shí)后添,整個(gè)操作就可能不會(huì)繼續(xù)執(zhí)行下去笨枯,而已經(jīng)進(jìn)行的操作造成的副作用就可能造成數(shù)據(jù)更新的丟失或者錯(cuò)誤。
事務(wù)其實(shí)和一個(gè)操作沒有什么太大的區(qū)別,它是一系列的數(shù)據(jù)庫(kù)操作(可以理解為 SQL)的集合馅精,如果事務(wù)不具備原子性严嗜,那么就沒辦法保證同一個(gè)事務(wù)中的所有操作都被執(zhí)行或者未被執(zhí)行了,整個(gè)數(shù)據(jù)庫(kù)系統(tǒng)就既不可用也不可信洲敢。
隔離性
事務(wù)的隔離性要求每個(gè)讀寫事務(wù)的對(duì)象對(duì)其他事務(wù)的操作對(duì)象能互相分離漫玄,即該事務(wù)提交前對(duì)其他事物都不可見,通常這使用鎖來實(shí)現(xiàn)压彭。當(dāng)前數(shù)據(jù)庫(kù)系統(tǒng)中都提供一種粒度鎖的策略睦优,允許事務(wù)僅鎖住一個(gè)實(shí)體對(duì)象的子集,以此來提高事物之間的并發(fā)度壮不。
持久性
既然是數(shù)據(jù)庫(kù)汗盘,那么一定對(duì)數(shù)據(jù)的持久存儲(chǔ)有著非常強(qiáng)烈的需求,如果數(shù)據(jù)被寫入到數(shù)據(jù)庫(kù)中询一,那么數(shù)據(jù)一定能夠被安全存儲(chǔ)在磁盤上隐孽;而事務(wù)的持久性就體現(xiàn)在,一旦事務(wù)被提交健蕊,那么數(shù)據(jù)一定會(huì)被寫入到數(shù)據(jù)庫(kù)中并持久存儲(chǔ)起來菱阵。
當(dāng)事務(wù)已經(jīng)被提交之后,就無法再次回滾了缩功,唯一能夠撤回已經(jīng)提交的事務(wù)的方式就是創(chuàng)建一個(gè)相反的事務(wù)對(duì)原操作進(jìn)行『補(bǔ)償』晴及,這也是事務(wù)持久性的體現(xiàn)之一。
一致性
一致性是指事務(wù)將數(shù)據(jù)庫(kù)從一種狀態(tài)變?yōu)橄乱环N狀態(tài)掂之。在事務(wù)開始之前和事務(wù)結(jié)束之后抗俄,數(shù)據(jù)庫(kù)的完整性約束沒有被破壞。即在事務(wù)的執(zhí)行的前后以及過程中不會(huì)違背對(duì)數(shù)據(jù)完整性的約束世舰,所有對(duì)數(shù)據(jù)庫(kù)寫入的操作都應(yīng)該是合法的动雹,并不能產(chǎn)生不合法的數(shù)據(jù)狀態(tài)。
事務(wù)的實(shí)現(xiàn)
事務(wù)的隔離性由鎖來實(shí)現(xiàn)跟压,原子性胰蝠、一致性、持久性通過數(shù)據(jù)庫(kù)的redo log和undo log來完成震蒋。redo log稱為重做日志茸塞,用來保證事物的原子性和持久性。undo log稱為回滾日志查剖,用來保證事務(wù)的一致性钾虐,幫助事務(wù)回滾及MVCC的功能。
redo恢復(fù)提交事務(wù)修改的頁操作笋庄,而undo回滾行記錄到某個(gè)特定版本效扫。因此兩者記錄的內(nèi)容不同倔监,redo通常是物理日志,記錄的是頁的物理修改操作菌仁。undo是邏輯日志浩习,根據(jù)每行記錄進(jìn)行記錄。
重做日志(redo log)
重做日志由兩部分組成:一是內(nèi)存中的重做日志緩沖济丘,其是易失的谱秽;二是重做日志文件,其是持久的摹迷。
當(dāng)事務(wù)提交時(shí)疟赊,必須先將該事物的所有日志寫入到重做日志文件進(jìn)行持久化,呆事物的COMMIT操作完成才算完成泪掀。
這里的事務(wù)日志指的就是redo log和undo log听绳。
當(dāng)我們?cè)谝粋€(gè)事務(wù)中嘗試對(duì)數(shù)據(jù)進(jìn)行修改時(shí),它會(huì)先將數(shù)據(jù)從磁盤讀入內(nèi)存异赫,并更新內(nèi)存中緩存的數(shù)據(jù)椅挣,然后生成一條重做日志并寫入重做日志緩存,當(dāng)事務(wù)真正提交時(shí)塔拳,MySQL 會(huì)將重做日志緩存中的內(nèi)容刷新到重做日志文件鼠证,再將內(nèi)存中的數(shù)據(jù)更新到磁盤上,圖中的第 4靠抑、5 步就是在事務(wù)提交時(shí)執(zhí)行的量九。
在 InnoDB 中,重做日志都是以 512 字節(jié)的塊的形式進(jìn)行存儲(chǔ)的颂碧,同時(shí)因?yàn)閴K的大小與磁盤扇區(qū)大小相同荠列,所以重做日志的寫入可以保證原子性,不會(huì)由于機(jī)器斷電導(dǎo)致重做日志僅寫入一半并留下臟數(shù)據(jù)载城。
除了所有對(duì)數(shù)據(jù)庫(kù)的修改會(huì)產(chǎn)生重做日志肌似,因?yàn)榛貪L日志也是需要持久存儲(chǔ)的,它們也會(huì)創(chuàng)建對(duì)應(yīng)的重做日志诉瓦,在發(fā)生錯(cuò)誤后川队,數(shù)據(jù)庫(kù)重啟時(shí)會(huì)從重做日志中找出未被更新到數(shù)據(jù)庫(kù)磁盤中的日志重新執(zhí)行以滿足事務(wù)的持久性。
在MySQL數(shù)據(jù)庫(kù)中還有一種二進(jìn)制日志睬澡,其用來進(jìn)行POINT-IN-TIME的恢復(fù)及主從復(fù)制環(huán)境的建立固额。從表面上看其和重做日志非常相似,都是記錄了對(duì)于數(shù)據(jù)庫(kù)操作的日志煞聪。然而本質(zhì)上來看斗躏,兩者有著非常大的不同。
首先昔脯,重做日志是在InnoDB存儲(chǔ)引擎層產(chǎn)生瑟捣,而二進(jìn)制日志是在MySQL數(shù)據(jù)庫(kù)的上層產(chǎn)生馋艺,并且二進(jìn)制日志不僅僅針對(duì)于InnoDB存儲(chǔ)引擎栅干,MySQL數(shù)據(jù)庫(kù)中的任意存儲(chǔ)引擎對(duì)于數(shù)據(jù)庫(kù)的更改都會(huì)產(chǎn)生二進(jìn)制日志迈套。
其次,兩種日志記錄的內(nèi)容形式不同碱鳞,二進(jìn)制日志是一種邏輯日志桑李,記錄對(duì)應(yīng)的SQL語句。重做日志是物理格式日志窿给,記錄的是對(duì)于每個(gè)頁的修改贵白。
此外,二進(jìn)制日志只在事務(wù)提交完成后進(jìn)行一次寫入崩泡。而重做日志在事務(wù)進(jìn)行中不斷地被寫入禁荒。
回滾日志(undo log)
想要保證事務(wù)的原子性,就需要在異常發(fā)生時(shí)角撞,對(duì)已經(jīng)執(zhí)行的操作進(jìn)行回滾呛伴,而在 MySQL 中,恢復(fù)機(jī)制是通過回滾日志(undo log)實(shí)現(xiàn)的谒所,所有事務(wù)進(jìn)行的修改都會(huì)先記錄到這個(gè)回滾日志中热康,然后在對(duì)數(shù)據(jù)庫(kù)中的對(duì)應(yīng)行進(jìn)行寫入。
這個(gè)過程其實(shí)非常好理解劣领,為了能夠在發(fā)生錯(cuò)誤時(shí)撤銷之前的全部操作姐军,肯定是需要將之前的操作都記錄下來的,這樣在發(fā)生錯(cuò)誤時(shí)才可以回滾尖淘。
回滾日志除了能夠在發(fā)生錯(cuò)誤或者用戶執(zhí)行ROLLBACK時(shí)提供回滾相關(guān)的信息奕锌,它還能夠在整個(gè)系統(tǒng)發(fā)生崩潰、數(shù)據(jù)庫(kù)進(jìn)程直接被殺死后村生,當(dāng)用戶再次啟動(dòng)數(shù)據(jù)庫(kù)進(jìn)程時(shí)惊暴,還能夠立刻通過查詢回滾日志將之前未完成的事務(wù)進(jìn)行回滾,這也就需要回滾日志必須先于數(shù)據(jù)持久化到磁盤上梆造,是我們需要先寫日志后寫數(shù)據(jù)庫(kù)的主要原因缴守。
回滾日志并不能將數(shù)據(jù)庫(kù)物理地恢復(fù)到執(zhí)行語句或者事務(wù)之前的樣子;它是邏輯日志镇辉,當(dāng)回滾日志被使用時(shí)屡穗,它只會(huì)按照日志邏輯地將數(shù)據(jù)庫(kù)中的修改撤銷掉看,可以理解為忽肛,我們?cè)谑聞?wù)中使用的每一條INSERT都對(duì)應(yīng)了一條DELETE村砂,每一條UPDATE也都對(duì)應(yīng)一條相反的UPDATE語句。
這是因?yàn)樵诖罅渴聞?wù)并發(fā)時(shí)屹逛,一個(gè)事務(wù)在修改當(dāng)前一個(gè)頁中某幾條記錄础废,同時(shí)還有別的事務(wù)在對(duì)同一個(gè)頁中另幾條記錄進(jìn)行修改汛骂。此時(shí)不能將頁回滾到事務(wù)開始的樣子,因?yàn)檫@樣會(huì)影響其他的事務(wù)正在進(jìn)行的工作评腺。
重做日志與回滾日志
發(fā)生錯(cuò)誤或者需要回滾的事務(wù)能夠利用回滾日志進(jìn)行回滾(原子性)帘瞭;
在事務(wù)提交后,數(shù)據(jù)沒來得及寫會(huì)磁盤就宕機(jī)時(shí)蒿讥,在下次重新啟動(dòng)后能夠利用重做日志恢復(fù)數(shù)據(jù)(持久性)蝶念;
重做日志存放在重做日志文件中,而回滾日志存放在數(shù)據(jù)庫(kù)內(nèi)部的一個(gè)特殊段(segment)中芋绸,這個(gè)段稱為undo段媒殉。undo段位于共享表空間中。
在數(shù)據(jù)庫(kù)中摔敛,這兩種日志經(jīng)常都是一起工作的廷蓉,我們可以將它們整體看做一條事務(wù)日志,其中包含了事務(wù)的 ID马昙、修改的行元素以及修改前后的值桃犬。
對(duì)事務(wù)操作的統(tǒng)計(jì)
由于InnoDB存儲(chǔ)引擎是支持事務(wù)的,因此InnoDB存儲(chǔ)引擎的應(yīng)用需要在考慮每秒請(qǐng)求數(shù)(QPS)的同時(shí)给猾,關(guān)注每秒事務(wù)處理(TPS)的能力疫萤。
計(jì)算TPS的方法是(com_commit+com_rollback)/time。
事務(wù)的隔離級(jí)別
數(shù)據(jù)庫(kù)的隔離性和一致性其實(shí)是一個(gè)需要開發(fā)者去權(quán)衡的問題敢伸,為數(shù)據(jù)庫(kù)提供什么樣的隔離性層級(jí)也就決定了數(shù)據(jù)庫(kù)的性能以及可以達(dá)到什么樣的一致性扯饶;
在 SQL 標(biāo)準(zhǔn)中定義了四種數(shù)據(jù)庫(kù)的事務(wù)的隔離級(jí)別:READ UNCOMMITED、READ COMMITED池颈、REPEATABLE READ?和?SERIALIZABLE尾序;每個(gè)事務(wù)的隔離級(jí)別其實(shí)都比上一級(jí)多解決了一個(gè)問題:
RAED UNCOMMITED:可能會(huì)讀到未提交的行(Dirty Read),即可能發(fā)生臟讀躯砰;
READ COMMITED:沒有臟讀每币,但多次讀取某一行的值可能會(huì)發(fā)生變化,即可能發(fā)生不可重復(fù)讀琢歇;
REPEATABLE READ:可重復(fù)讀兰怠,每次讀取同一行的值都是不變的,但可能讀取到之前沒有的行李茫,即可能發(fā)生幻讀揭保;
SERIALIZABLE:解決了幻讀的問題;
由于鎖的存在魄宏,以上的所有的事務(wù)隔離級(jí)別都不允許臟寫入(Dirty Write)秸侣,也就是當(dāng)前事務(wù)更新了另一個(gè)事務(wù)已經(jīng)更新但是還未提交的數(shù)據(jù)。
大部分的數(shù)據(jù)庫(kù)中都使用了 READ COMMITED 作為默認(rèn)的事務(wù)隔離級(jí)別,但是 InnoDB 使用了 REPEATABLE READ 作為默認(rèn)配置味榛,但與標(biāo)準(zhǔn)SQL不同的是椭坚,InnoDB存儲(chǔ)引擎在REPEATABLE READ事務(wù)隔離級(jí)別下,使用Next-key Lock鎖的算法搏色,因此避免幻讀的產(chǎn)生善茎。
一般情況來說,從 RAED UNCOMMITED 到 SERIALIZABLE继榆,隨著事務(wù)隔離級(jí)別變得越來越嚴(yán)格巾表,數(shù)據(jù)庫(kù)對(duì)于并發(fā)執(zhí)行事務(wù)的性能也逐漸下降。
參考