摘要:關(guān)系型數(shù)據(jù)庫中的事務(wù)管理詳解并發(fā)控制與事務(wù)日志數(shù)據(jù)庫系統(tǒng)的萌芽出現(xiàn)于年代。并發(fā)控制并發(fā)控制旨在針對(duì)數(shù)據(jù)庫中對(duì)事務(wù)并行的場(chǎng)景,保證中的一致性與隔離性。絕大部分?jǐn)?shù)據(jù)庫會(huì)采用鎖或者數(shù)據(jù)版本控制的方式來處理并發(fā)控制問題。
本文節(jié)選自:關(guān)系型數(shù)據(jù)庫理論 https://url.wx-coder.cn/DJNQn ,涉及引用/整理的文章列舉在了 Database-List。關(guān)系型數(shù)據(jù)庫中的事務(wù)管理詳解:并發(fā)控制與事務(wù)日志
數(shù)據(jù)庫系統(tǒng)的萌芽出現(xiàn)于 60 年代。當(dāng)時(shí)計(jì)算機(jī)開始廣泛地應(yīng)用于數(shù)據(jù)管理,對(duì)數(shù)據(jù)的共享提出了越來越高的要求。傳統(tǒng)的文件系統(tǒng)已經(jīng)不能滿足人們的需要。能夠統(tǒng)一管理和共享數(shù)據(jù)的數(shù)據(jù)庫管理系統(tǒng)(DBMS)應(yīng)運(yùn)而生。1961 年通用電氣公司(General ElectricCo.)的 Charles Bachman 成功地開發(fā)出世界上第一個(gè)網(wǎng)狀 DBMS 也是第一個(gè)數(shù)據(jù)庫管理系統(tǒng)—— 集成數(shù)據(jù)存儲(chǔ)(Integrated DataStore IDS),奠定了網(wǎng)狀數(shù)據(jù)庫的基礎(chǔ)。
1970 年,IBM 的研究員 E.F.Codd 博士在刊物 Communication of the ACM 上發(fā)表了一篇名為“A Relational Modelof Data for Large Shared Data Banks”的論文,提出了關(guān)系模型的概念,奠定了關(guān)系模型的理論基礎(chǔ)。1974 年,IBM 的 Ray Boyce 和 DonChamberlin 將 Codd 關(guān)系數(shù)據(jù)庫的 12 條準(zhǔn)則的數(shù)學(xué)定義以簡(jiǎn)單的關(guān)鍵字語法表現(xiàn)出來,里程碑式地提出了 SQL(Structured Query Language)語言。在很長(zhǎng)的時(shí)間內(nèi),關(guān)系數(shù)據(jù)庫(如 MySQL 和 Oracle)對(duì)于開發(fā)任何類型的應(yīng)用程序都是首選,巨石型架構(gòu)也是應(yīng)用程序開發(fā)的標(biāo)準(zhǔn)架構(gòu)。
本文即是對(duì)關(guān)系型數(shù)據(jù)庫中的事務(wù)管理相關(guān)內(nèi)容進(jìn)行討論。
事務(wù)基礎(chǔ) ACID事務(wù)提供一種全做,或不做(All or Nothing)的機(jī)制,即將一個(gè)活動(dòng)涉及的所有操作納入到一個(gè)不可分割的執(zhí)行單元,組成事務(wù)的所有操作只有在所有操作均能正常執(zhí)行的情況下方能提交,只要其中任一操作執(zhí)行失敗,都將導(dǎo)致整個(gè)事務(wù)的回滾。數(shù)據(jù)庫事務(wù)具有 ACID 屬性,即原子性(Atomic)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability),在分布式事務(wù) https://url.wx-coder.cn/7p8Xx 中我們也會(huì)討論分布式系統(tǒng)中應(yīng)該如何實(shí)現(xiàn)事務(wù)機(jī)制。
ACID 包含了描述事務(wù)操作的整體性的原子性,描述事務(wù)操作下數(shù)據(jù)的正確性的一致性,描述事務(wù)并發(fā)操作下數(shù)據(jù)的正確性的隔離性,描述事務(wù)對(duì)數(shù)據(jù)修改的可靠性的持久性。針對(duì)數(shù)據(jù)庫的一系列操作提供了一種從失敗狀態(tài)恢復(fù)到正常狀態(tài)的方法,使數(shù)據(jù)庫在異常狀態(tài)下也能夠保持?jǐn)?shù)據(jù)的一致性,且面對(duì)并發(fā)訪問時(shí),數(shù)據(jù)庫能夠提供一種隔離方法,避免彼此間的操作互相干擾。
原子性(Atomicity):整個(gè)事務(wù)中的所有操作,要么全部完成,要么全部不完成,不可能停滯在中間某個(gè)環(huán)節(jié)。事務(wù)在執(zhí)行過程中發(fā)生錯(cuò)誤,會(huì)被回滾(Rollback)到事務(wù)開始前的狀態(tài),就像這個(gè)事務(wù)從來沒有執(zhí)行過一樣。例如:銀行轉(zhuǎn)賬,從 A 賬戶轉(zhuǎn) 100 元至 B 賬戶,分為兩個(gè)步驟:從 A 賬戶取 100 元;存入 100 元至 B 賬戶。這兩步要么一起完成,要么一起不完成。
一致性(Consistency):在事務(wù)開始之前和事務(wù)結(jié)束以后,數(shù)據(jù)庫數(shù)據(jù)的一致性約束沒有被破壞;即當(dāng)事務(wù) A 與 B 同時(shí)運(yùn)行,無論 A,B 兩個(gè)事務(wù)的結(jié)束順序如何,數(shù)據(jù)庫都會(huì)達(dá)到統(tǒng)一的狀態(tài)。
隔離性(Isolation):數(shù)據(jù)庫允許多個(gè)并發(fā)事務(wù)同時(shí)對(duì)數(shù)據(jù)進(jìn)行讀寫和修改的能力,如果一個(gè)事務(wù)要訪問的數(shù)據(jù)正在被另外一個(gè)事務(wù)修改,只要另外一個(gè)事務(wù)未提交,它所訪問的數(shù)據(jù)就不受未提交事務(wù)的影響。隔離性可以防止多個(gè)事務(wù)并發(fā)執(zhí)行時(shí)由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致。 例如:現(xiàn)有有個(gè)交易是從 A 賬戶轉(zhuǎn) 100 元至 B 賬戶,在這個(gè)交易事務(wù)還未完成的情況下,如果此時(shí) B 查詢自己的賬戶,是看不到新增加的 100 元的。
持久性(Durability):當(dāng)某個(gè)事務(wù)一旦提交,無論數(shù)據(jù)庫崩潰還是其他未知情況,該事務(wù)的結(jié)果都能夠被持久化保存下來。
隔離級(jí)別SQL 標(biāo)準(zhǔn)定義了 4 類隔離級(jí)別,包括了一些具體規(guī)則,用來限定事務(wù)內(nèi)外的哪些改變是可見的,哪些是不可見的。低級(jí)別的隔離級(jí)一般支持更高的并發(fā)處理,并擁有更低的系統(tǒng)開銷。
隔離級(jí)別 | 臟讀(Dirty Read ) | 不可重復(fù)讀(NonRepeatable Read ) | 幻讀(Phantom Read ) |
---|---|---|---|
未提交讀(Read Uncommitted) | 可能 | 可能 | 可能 |
提交讀(Read Committed ) | 不可能 | 可能 | 可能 |
可重復(fù)讀(Repeatable Read ) | 不可能 | 不可能 | 可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
在該隔離級(jí)別,所有事務(wù)都可以看到其他未提交事務(wù)的執(zhí)行結(jié)果。本隔離級(jí)別很少用于實(shí)際應(yīng)用,因?yàn)樗男阅芤膊槐绕渌?jí)別好多少。讀取未提交的數(shù)據(jù),也被稱之為臟讀(Dirty Read)。
Read Committed 提交讀這是大多數(shù)數(shù)據(jù)庫系統(tǒng)的默認(rèn)隔離級(jí)別比如 Sql Server, Oracle 等,但不是 MySQL 默認(rèn)的。它滿足了隔離的簡(jiǎn)單定義:一個(gè)事務(wù)只能看見已經(jīng)提交事務(wù)所做的改變。這種隔離級(jí)別也支持所謂的不可重復(fù)讀(Nonrepeatable Read),因?yàn)橥皇聞?wù)的其他實(shí)例在該實(shí)例處理其間可能會(huì)有新的 Commit,所以同一查詢可能返回不同結(jié)果。
Repeatable Read | 重復(fù)讀當(dāng)隔離級(jí)別設(shè)置為 Repeatable Read 時(shí),可以避免不可重復(fù)讀。不可重復(fù)讀是指事務(wù) T1 讀取數(shù)據(jù)后,事務(wù) T2 執(zhí)行更新操作,使 T1 無法再現(xiàn)前一次讀取結(jié)果。具體地講,不可重復(fù)讀包括三種情況:
事務(wù) T1 讀取某一數(shù)據(jù)后,事務(wù) T2 對(duì)其做了修改,當(dāng)事務(wù) T1 再次讀該數(shù)據(jù)時(shí),得到與前一次不同的值。例如,T1 讀取 B=100 進(jìn)行運(yùn)算,T2 讀取同一數(shù)據(jù) B,對(duì)其進(jìn)行修改后將 B=200 寫回?cái)?shù)據(jù)庫。T1 為了對(duì)讀取值校對(duì)重讀 B,B 已為 200,與第一次讀取值不一致。
事務(wù) T1 按一定條件從數(shù)據(jù)庫中讀取了某些數(shù)據(jù)記錄后,事務(wù) T2 刪除了其中部分記錄,當(dāng) T1 再次按相同條件讀取數(shù)據(jù)時(shí),發(fā)現(xiàn)某些記錄神密地消失了。
事務(wù) T1 按一定條件從數(shù)據(jù)庫中讀取某些數(shù)據(jù)記錄后,事務(wù) T2 插入了一些記錄,當(dāng) T1 再次按相同條件讀取數(shù)據(jù)時(shí),發(fā)現(xiàn)多了一些記錄,也就是幻讀。
這是 MySQL 的默認(rèn)事務(wù)隔離級(jí)別,它確保在一個(gè)事務(wù)內(nèi)的相同查詢條件的多次查詢會(huì)看到同樣的數(shù)據(jù)行,都是事務(wù)開始時(shí)的數(shù)據(jù)快照。雖然 Repeatable Read 避免了不可重復(fù)讀,但還有可能出現(xiàn)幻讀。簡(jiǎn)單說,就是當(dāng)某個(gè)事務(wù)在讀取某個(gè)范圍內(nèi)的記錄時(shí),另外的一個(gè)事務(wù)又在該范圍內(nèi)插入新的記錄。在之前的事務(wù)在讀取該范圍的記錄時(shí),就會(huì)產(chǎn)生幻行,InnoDB 通過間隙鎖(next-key locking)策略防止幻讀的出現(xiàn)。
Serializable | 序列化Serializable 是最高的事務(wù)隔離級(jí)別,它通過強(qiáng)制事務(wù)排序,使之不可能相互沖突,從而解決幻讀問題。簡(jiǎn)言之,它是在每個(gè)讀的數(shù)據(jù)行上加上共享鎖。在這個(gè)級(jí)別,可能導(dǎo)致大量的超時(shí)現(xiàn)象和鎖競(jìng)爭(zhēng)。該隔離級(jí)別代價(jià)也花費(fèi)最高,性能很低,一般很少使用,在該級(jí)別下,事務(wù)順序執(zhí)行,不僅可以避免臟讀、不可重復(fù)讀,還避免了幻讀。
并發(fā)控制并發(fā)控制旨在針對(duì)數(shù)據(jù)庫中對(duì)事務(wù)并行的場(chǎng)景,保證 ACID 中的一致性(Consistency)與隔離性(Isolation)。假如所有的事務(wù)都僅進(jìn)行數(shù)據(jù)讀取,那么事務(wù)之間并不會(huì)有沖突;而一旦某個(gè)事務(wù)讀取了正在被其他事務(wù)修改的數(shù)據(jù)或者兩個(gè)事務(wù)修改了相同的數(shù)據(jù),那么數(shù)據(jù)庫就必須來保證事務(wù)之間的隔離,來避免某個(gè)事務(wù)因?yàn)槲匆娮钚碌臄?shù)據(jù)而造成的誤操作。解決并發(fā)控制問題最理想的方式就是能夠每當(dāng)某個(gè)事務(wù)被創(chuàng)建或者停止的時(shí)候,監(jiān)控所有事務(wù)的所有操作,判斷是否存在沖突的事務(wù),然后對(duì)沖突事務(wù)中的操作進(jìn)行重排序以盡可能少地減少?zèng)_突,而后以特定的順序運(yùn)行這些操作。絕大部分?jǐn)?shù)據(jù)庫會(huì)采用鎖(Locks)或者數(shù)據(jù)版本控制(Data Versioning)的方式來處理并發(fā)控制問題。
數(shù)據(jù)庫技術(shù)中主流的三種并發(fā)控制技術(shù)分別是: Multi-version Concurrency Control (MVCC), Strict Two-Phase Locking (S2PL), 以及 Optimistic Concurrency Control (OCC),每種技術(shù)也都有很多的變種。在 MVCC 中,每次寫操作都會(huì)在舊的版本之上創(chuàng)建新的版本,并且會(huì)保留舊的版本。當(dāng)某個(gè)事務(wù)需要讀取數(shù)據(jù)時(shí),數(shù)據(jù)庫系統(tǒng)會(huì)從所有的版本中選取出符合該事務(wù)隔離級(jí)別要求的版本。MVCC 的最大優(yōu)勢(shì)在于讀并不會(huì)阻塞寫,寫也不會(huì)阻塞讀;而像 S2PL 這樣的系統(tǒng),寫事務(wù)會(huì)事先獲取到排他鎖,從而會(huì)阻塞讀事務(wù)。PostgreSQL 以及 Oracle 等 RDBMS 實(shí)際使用了所謂的 Snapshot Isolation(SI)這個(gè) MVCC 技術(shù)的變種。Oracle 引入了額外的 Rollback Segments,當(dāng)寫入新的數(shù)據(jù)時(shí),老版本的數(shù)據(jù)會(huì)被寫入到 Rollback Segment 中,隨后再被覆寫到實(shí)際的數(shù)據(jù)塊。PostgreSQL 則是使用了相對(duì)簡(jiǎn)單的實(shí)現(xiàn)方式,新的數(shù)據(jù)對(duì)象會(huì)被直接插入到關(guān)聯(lián)的 Table Page 中;而在讀取表數(shù)據(jù)的時(shí)候,PostgreSQL 會(huì)通過可見性檢測(cè)規(guī)則(Visibility Check Rules)來選擇合適的版本。
鎖管理器(Lock Manager)基于鎖的方式基礎(chǔ)理念為:如果某個(gè)事務(wù)需要數(shù)據(jù),則對(duì)數(shù)據(jù)加鎖,操作完畢后釋放鎖;如果過程中其他事務(wù)需要鎖,則需要等到該事務(wù)釋放數(shù)據(jù)鎖,這種鎖也就是所謂的排他鎖(Exclusive Lock)。不過使用排他鎖會(huì)帶來極大的性能損耗,其會(huì)導(dǎo)致其他那些僅需要讀取數(shù)據(jù)的事務(wù)也陷入等待。另一種加鎖的方式稱為共享鎖(Shared Lock),當(dāng)兩個(gè)事務(wù)都聲明讀取數(shù)據(jù) A 時(shí),它們會(huì)分別給 A 添加共享鎖;對(duì)于此事需要修改數(shù)據(jù) A 的事務(wù)而言,它必須等待所有的共享鎖釋放完畢之后才能針對(duì)數(shù)據(jù) A 添加排他鎖。同樣地,對(duì)于已經(jīng)被設(shè)置了排他鎖的數(shù)據(jù),僅有讀取請(qǐng)求的事務(wù)同樣需要等到該排他鎖被釋放后才能添加共享鎖。
從鎖定的數(shù)據(jù)范圍鎖粒度(Lock Granularity)來看分為:
表鎖:管理鎖的開銷最小,同時(shí)允許的并發(fā)量也最小的鎖機(jī)制。MyIsam 存儲(chǔ)引擎使用的鎖機(jī)制。當(dāng)要寫入數(shù)據(jù)時(shí),把整個(gè)表都鎖上,此時(shí)其他讀、寫動(dòng)作一律等待。在 MySql 中,除了 MyIsam 存儲(chǔ)引擎使用這種鎖策略外,MySql 本身也使用表鎖來執(zhí)行某些特定動(dòng)作,比如 ALTER TABLE.
行鎖:可以支持最大并發(fā)的鎖策略。InnoDB 和 Falcon 兩種存儲(chǔ)引擎都采用這種策略。
鎖管理器(Lock Manager)即負(fù)責(zé)分配與釋放鎖,大部分?jǐn)?shù)據(jù)庫是以哈希表的方式來存放持有鎖以及等待鎖的事務(wù)。在 MySQL 實(shí)戰(zhàn) https://url.wx-coder.cn/Tu5dq 中我們也討論了如何觸發(fā)鎖機(jī)制,譬如查詢加鎖,select * from testlock where id=1 for update;,即查詢時(shí)不允許更改,該語句在自動(dòng)提交為 off 或事務(wù)中生效,相當(dāng)于更改操作,模擬加鎖;而更像類操作 update testlock name=name; 則是會(huì)自動(dòng)加鎖。
同樣的,參考并發(fā)編程導(dǎo)論 https://url.wx-coder.cn/Yagu8 中的討論,只要存在鎖的地方就會(huì)存在死鎖(Deadlock)的可能性:
在發(fā)生死鎖的時(shí)候,鎖管理器會(huì)根據(jù)一定的規(guī)則來選取應(yīng)該終止或者被回滾的事務(wù):
根據(jù)是否能最小化需要被回滾的數(shù)據(jù);
根據(jù)事務(wù)發(fā)生的先后順序;
根據(jù)事務(wù)執(zhí)行所需要的時(shí)間,以盡可能避免饑餓狀態(tài)的出現(xiàn);
根據(jù)需要回滾的關(guān)聯(lián)事務(wù)的數(shù)目;
避免死鎖,確保純隔離的最簡(jiǎn)單方法是在事務(wù)開始時(shí)獲取鎖并在事務(wù)結(jié)束時(shí)釋放鎖。這意味著事務(wù)必須在啟動(dòng)之前等待其所有鎖,并且在事務(wù)結(jié)束時(shí)釋放事務(wù)持有的鎖,這種方式會(huì)浪費(fèi)很多時(shí)間來等待所有鎖。實(shí)際的數(shù)據(jù)庫,譬如 DB2 與 SQL Server 中往往采取兩階段鎖協(xié)議(Two-Phase Locking Protocol),即將事務(wù)過程切分為兩個(gè)階段:
Growing Phase: 該階段僅可以獲取鎖,而不可以釋放鎖。
Shrinking Phase: 該階段僅可以釋放鎖,而不可以獲取新的鎖。
該策略能夠減少其他事務(wù)等待鎖的時(shí)間,并且避免某個(gè)事務(wù)在中途修改了并不是它初次申請(qǐng)的數(shù)據(jù)。
MVCC在并發(fā)編程導(dǎo)論 https://url.wx-coder.cn/Yagu8 中我們討論了兩種不同類型的鎖:樂觀鎖(Optimistic Lock)與悲觀鎖(Pessimistic Lock),前文介紹的各種鎖即是悲觀鎖,而 MVCC(Multiple Version Concurrency Control) 這樣的基于數(shù)據(jù)版本的鎖則是樂觀鎖,它能夠保證讀寫操作之間不會(huì)相互阻塞:
每個(gè)事務(wù)都可以在同一時(shí)間修改相同的數(shù)據(jù);
每個(gè)事務(wù)會(huì)保有其需要的數(shù)據(jù)副本;
如果兩個(gè)事務(wù)修改了相同的數(shù)據(jù),那么僅有單個(gè)更改操作會(huì)被接收,另一個(gè)操作會(huì)被回滾或者重新執(zhí)行。
樂觀鎖,大多是基于數(shù)據(jù)版本(Version)記錄機(jī)制實(shí)現(xiàn)。數(shù)據(jù)版本即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個(gè) version 字段來實(shí)現(xiàn)。讀取出數(shù)據(jù)時(shí),將此版本號(hào)一同讀出,之后更新時(shí),對(duì)此版本號(hào)加一。此時(shí),將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對(duì)應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對(duì),如果提交的數(shù)據(jù)版本號(hào)大于數(shù)據(jù)庫表當(dāng)前版本號(hào),則予以更新,否則認(rèn)為是過期數(shù)據(jù)。而 PostgreSQL 中則是依賴于 txid 以及 Commit Log 結(jié)合而成的可見性檢測(cè)機(jī)制來實(shí)現(xiàn) MVCC,詳情可以參考 PostgreSQL 架構(gòu)機(jī)制 https://url.wx-coder.cn/SgRDQ 中關(guān)于并發(fā)控制相關(guān)的介紹。
日志管理器(Log Manager)數(shù)據(jù)庫事務(wù)由具體的 DBMS 系統(tǒng)來保障操作的原子性,同一個(gè)事務(wù)當(dāng)中,如果有某個(gè)操作執(zhí)行失敗,則事務(wù)當(dāng)中的所有操作都需要進(jìn)行回滾,回到事務(wù)執(zhí)行前的狀態(tài)。導(dǎo)致事務(wù)失敗的原因有很多,可能是因?yàn)樾薷牟环媳淼募s束規(guī)則,也有可能是網(wǎng)絡(luò)異常,甚至是存儲(chǔ)介質(zhì)故障等,而一旦事務(wù)失敗,則需要對(duì)所有已作出的修改操作進(jìn)行還原,使數(shù)據(jù)庫的狀態(tài)恢復(fù)到事務(wù)執(zhí)行前的狀態(tài),以保障數(shù)據(jù)的一致性,使修改操作要么全部成功、要么全部失敗,避免存在中間狀態(tài)。
訪問磁盤中的數(shù)據(jù)往往速度較慢,換言之,內(nèi)存中數(shù)據(jù)的訪問速度還是遠(yuǎn)快于 SSD 中的數(shù)據(jù)訪問速度?;谶@個(gè)考量,基本上所有數(shù)據(jù)庫引擎都盡可能地避免訪問磁盤數(shù)據(jù)。并且無論數(shù)據(jù)庫表還是數(shù)據(jù)庫索引都被劃分為了固定大小的數(shù)據(jù)頁(譬如 8 KB)。當(dāng)我們需要讀取表或者索引中的數(shù)據(jù)時(shí),關(guān)系型數(shù)據(jù)庫會(huì)將磁盤中的數(shù)據(jù)頁映射入存儲(chǔ)緩沖區(qū)。當(dāng)我們需要修改數(shù)據(jù)時(shí),關(guān)系型數(shù)據(jù)庫首先會(huì)修改內(nèi)存頁中的數(shù)據(jù),然后利用 fsync 這樣的同步工具將改變同步回磁盤中。
不過一旦數(shù)據(jù)庫突發(fā)崩潰,那么緩沖區(qū)中的數(shù)據(jù)也就丟失,最終打破了事務(wù)的持久性。另一個(gè)極端情況而言,我們也可以隨時(shí)將數(shù)據(jù)寫入到磁盤中,但是在崩潰的時(shí)候,很可能只寫入了一半的數(shù)據(jù),而打破了事務(wù)的原子性(Atomicity)。為了解決這個(gè)問題,我們可以采取以下兩種方案:
影子拷貝(Shadow Copies/Pages):每個(gè)事務(wù)會(huì)創(chuàng)建數(shù)據(jù)庫的部分拷貝,然后針對(duì)這些拷貝進(jìn)行操作。在發(fā)生異常的時(shí)候,這些拷貝會(huì)被移除;正常的情況下,數(shù)據(jù)庫則會(huì)立刻將這個(gè)拷貝寫入到磁盤然后移除老的數(shù)據(jù)塊。
事務(wù)日志(Transaction Log):所謂的事務(wù)日志即是獨(dú)立的存儲(chǔ)空間,在將數(shù)據(jù)寫入真正的數(shù)據(jù)表之外,數(shù)據(jù)庫都會(huì)將事務(wù)操作順序?qū)懭氲侥硞€(gè)日志文件中。
在實(shí)際情況下,Shadow Copies/Pages 會(huì)受到極大的磁盤限制,因此絕大部分?jǐn)?shù)據(jù)庫還是選擇了以事務(wù)日志的方式。
事務(wù)日志(Transaction Log)為了實(shí)現(xiàn)數(shù)據(jù)庫狀態(tài)的恢復(fù),DBMS 系統(tǒng)通常需要維護(hù)事務(wù)日志以追蹤事務(wù)中所有影響數(shù)據(jù)庫數(shù)據(jù)的操作,以便執(zhí)行失敗時(shí)進(jìn)行事務(wù)的回滾。以 MySQL 的 InnoDB 存儲(chǔ)引擎為例,InnoDB 存儲(chǔ)引擎通過預(yù)寫事務(wù)日志的方式,來保障事務(wù)的原子性、一致性以及持久性。它包含 Redo 日志和 Undo 日志,Redo 日志在系統(tǒng)需要的時(shí)候,對(duì)事務(wù)操作進(jìn)行重做,如當(dāng)系統(tǒng)宕機(jī)重啟后,能夠?qū)?nèi)存中還沒有持久化到磁盤的數(shù)據(jù)進(jìn)行恢復(fù),而 Undo 日志,則能夠在事務(wù)執(zhí)行失敗的時(shí)候,利用這些 Undo 信息,將數(shù)據(jù)還原到事務(wù)執(zhí)行前的狀態(tài)。
事務(wù)日志可以提高事務(wù)執(zhí)行的效率,存儲(chǔ)引擎只需要將修改行為持久到事務(wù)日志當(dāng)中,便可以只對(duì)該數(shù)據(jù)在內(nèi)存中的拷貝進(jìn)行修改,而不需要每次修改都將數(shù)據(jù)回寫到磁盤。這樣做的好處是,日志寫入是一小塊區(qū)域的順序 I/O,而數(shù)據(jù)庫數(shù)據(jù)的磁盤回寫則是隨機(jī) I/O,磁頭需要不停地移動(dòng)來尋找需要更新數(shù)據(jù)的位置,無疑效率更低,通過事務(wù)日志的持久化,既保障了數(shù)據(jù)存儲(chǔ)的可靠性,又提高了數(shù)據(jù)寫入的效率。
當(dāng)某個(gè)事務(wù)需要去更改數(shù)據(jù)表中某一行時(shí),未提交的改變會(huì)被寫入到內(nèi)存數(shù)據(jù)中,而之前的數(shù)據(jù)會(huì)被追加寫入到 Undo Log 文件中。Oracle 或者 MySQL 中使用了所謂 Undo Log 數(shù)據(jù)結(jié)構(gòu),而 SQL Server 中則是使用 Transaction Log 完成此項(xiàng)工作。PostgreSQL 并沒有 Undo Log,不過其內(nèi)建支持所謂多版本的表數(shù)據(jù),即同一行的數(shù)據(jù)可能同時(shí)存在多個(gè)版本??偠灾?,任何關(guān)系型數(shù)據(jù)庫都采用的類似的數(shù)據(jù)結(jié)構(gòu)都是為了允許回滾以及數(shù)據(jù)的原子性。
某個(gè)事務(wù)提交之后,內(nèi)存中的改變就需要同步到磁盤中。不過并不是所有的事務(wù)提交都會(huì)立刻觸發(fā)同步,過高頻次的同步反而會(huì)對(duì)應(yīng)用性能造成損傷。這里關(guān)系型數(shù)據(jù)庫就是依靠 Redo Log 來達(dá)成這一點(diǎn),它是一個(gè)僅允許追加寫入的基于磁盤的數(shù)據(jù)結(jié)構(gòu),它會(huì)記錄所有尚未執(zhí)行同步的事務(wù)操作。相較于一次性寫入固定數(shù)目的數(shù)據(jù)頁到磁盤中,順序地寫入到 Redo Log 會(huì)比隨機(jī)訪問快上很多。因此,關(guān)于事務(wù)的 ACID 特性的保證與應(yīng)用性能之間也就達(dá)成了較好的平衡。該數(shù)據(jù)結(jié)構(gòu)在 Oracle 與 MySQL 中就是叫 Redo Log,而 SQL Server 中則是由 Transaction Log 執(zhí)行,在 PostgreSQL 中則是使用 Write-Ahead Log(WAL)。下面我們繼續(xù)回到上面的那個(gè)問題,應(yīng)該在何時(shí)將內(nèi)存中的數(shù)據(jù)寫入到磁盤中。關(guān)系型數(shù)據(jù)庫系統(tǒng)往往使用檢查點(diǎn)來同步內(nèi)存的臟數(shù)據(jù)頁與磁盤中的對(duì)應(yīng)部分。為了避免 IO 阻塞,同步過程往往需要等待較長(zhǎng)的時(shí)間才能完成。因此,關(guān)系型數(shù)據(jù)庫需要保證即使在所有內(nèi)存臟頁同步到磁盤之前引擎就崩潰的時(shí)候不會(huì)發(fā)生數(shù)據(jù)丟失。同樣地,在每次數(shù)據(jù)庫重啟的時(shí)候,數(shù)據(jù)庫引擎會(huì)基于 Redo Log 重構(gòu)那些最后一次成功的檢查點(diǎn)以來所有的內(nèi)存數(shù)據(jù)頁。
WAL(Write-Ahead Logging)WAL 協(xié)議主要包含了以下三條規(guī)則:
每個(gè)數(shù)據(jù)庫中的修改操作都會(huì)產(chǎn)生一條記錄,該記錄必須在數(shù)據(jù)被寫入到數(shù)據(jù)庫之前被寫入到日志文件中;
所有的操作日志都必須嚴(yán)格按序記錄,即如果 A 記錄發(fā)生在 B 之前,那么 A 也必須在 B 之前被寫入到日志中;
在事務(wù)被提交之后,必須在日志寫入成功之后才能回復(fù)事務(wù)處理成功。
同樣可以參考 PostgreSQL 架構(gòu)機(jī)制 https://url.wx-coder.cn/SgRDQ 中有關(guān)于 WAL 的實(shí)例討論。
延伸閱讀歡迎關(guān)注某熊的技術(shù)之路公眾號(hào)或某熊的技術(shù)之路指北,讓我們一起前行。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/39030.html
摘要:關(guān)系型數(shù)據(jù)庫中的事務(wù)管理詳解并發(fā)控制與事務(wù)日志數(shù)據(jù)庫系統(tǒng)的萌芽出現(xiàn)于年代。并發(fā)控制并發(fā)控制旨在針對(duì)數(shù)據(jù)庫中對(duì)事務(wù)并行的場(chǎng)景,保證中的一致性與隔離性。絕大部分?jǐn)?shù)據(jù)庫會(huì)采用鎖或者數(shù)據(jù)版本控制的方式來處理并發(fā)控制問題。 本文節(jié)選自:關(guān)系型數(shù)據(jù)庫理論 https://url.wx-coder.cn/DJNQn ,涉及引用/整理的文章列舉在了 Database-List。 showImg(htt...
摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識(shí)點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對(duì)象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...
閱讀 1531·2021-11-24 09:38
閱讀 3379·2021-11-18 10:02
閱讀 3268·2021-09-22 15:29
閱讀 2956·2021-09-22 15:15
閱讀 1057·2021-09-13 10:25
閱讀 1875·2021-08-17 10:13
閱讀 2006·2021-08-04 11:13
閱讀 1986·2019-08-30 15:54