[譯]數據庫是如何工做(六)數據管理器

這步中,查詢管理器正在執行查詢並須要從表和索引中獲取數據。它這會要求數據管理器給它數據,但這有兩個問題:html

  • 關係數據庫使用一個事務模型。因此你有可能會有時拿不到數據由於正好那時有人在使用/修改數據。
  • 數據庫檢索是數據庫最慢的操做 。因此數據庫須要很聰明地在內存緩衝區中存取數據。

在這部分,咱們會看到關係數據庫是如何解決這兩個問題。我不會談及數據管理器獲取數據的方式,由於這不過重要(這篇文章已經夠長了)
mysql

緩存管理器

就像我以前說的,數據庫的主要瓶頸是磁盤 I/O。爲了提升性能,現代數據庫使用緩存管理器。算法

查詢管理器不會直接從系統中拿數據,而是去緩存管理器請求數據。緩存管理器有個叫緩衝池(buffer pool)的內存緩存。從內存中獲取數據會大大加快數據庫的速度 。但這很難給出一個具體的數量級,由於這取決於你須要的是哪一種操做:sql

  • 順序訪問(如:全局掃描) vs 隨機訪問(如:經過行ID直接訪問)
  • 讀 vs 寫

數據庫用的是什麼磁盤數據庫

  • 7.2k/10k/15k 轉的 HDD
  • 固態硬盤
  • RAID 1/5/...

但我仍是要說內存比磁盤快100到100k倍。 這又致使另外一個問題的出現(數據庫老是這樣。。。),緩存管理器須要在查詢執行器使用數以前,從內存中獲取數據;因此查詢管理器須要等待數據從慢磁盤中獲取緩存

預讀取數據

這個問題叫預讀取。查詢管理器知道將會須要數據了,由於它知道查詢的完整流程和磁盤上的數據的統計信息。構思以下:服務器

  • 當查詢執行器正在處理第一塊(bunch)的數據
  • 它會要求緩存管理器要預加載第二塊(bunch)的數據
  • 當開始處理第二塊的數據時
  • 它會要求緩存管理器要預加載第三塊(bunch)的數據,並告訴緩存管理器能夠第三塊的數據能夠從緩存中清理掉了
  • ...

緩存管理器會在緩衝區中存儲全部數據。爲了知道數據是否仍然須要,緩存管理器會爲數據添加了一個緩存日期(叫閂latch) 有時查詢執行器不知道須要什麼數據,有時候數據庫也不提供功能。相反他們會用推測預讀(例如:若是查詢執行器要數據 1,3,5,它可能在不久的未來會要數據 7,9,11) 又或者一個順序的預讀取(在這種狀況下,緩存管理器在一次請求後,簡單地加載下一個連續的數據)網絡

注意:緩存命中率不高並不老是意味着緩存不正常。有關更多信息,請閱讀 Oracle文檔併發

但,緩衝是的內存是有限的。所以,它須要將一些數據移走並加載新的數據。加載和清理緩存須要一點磁盤和網絡的I/O 成本。若是你有一個查詢要常常執行,使用這查詢的時候老是要加載數據清理數據,這也未免太沒效率的。爲了解決這個問題,現代數據庫使用一種緩衝區替換策略mvc

緩存區替換策略

LRU

LRU是指(Least Recently Used)最近用得最少的。這個算法背後的構想是在緩存中保留最近使用,這些數據更有可能會再次使用 下面是個直觀的例子

爲了便於理解,我會設計這些在緩衝區的數據沒有被閂(latch)鎖住(因此能被移除)。在這個簡單的例子中,這個緩衝區能夠存儲3個元素

1) 緩衝管理器用了數據1,而後把數據放到一個空的緩衝區
2) 緩衝管理器用了數據4,而後把數據放半滿載緩衝區
3) 緩衝管理器用了數據3,而後把數據放到半滿載緩衝
4) 緩衝管理器用了數據9,緩衝區已滿,* 因而將數據1移除,由於它是最先使用的數據* 。而後把數據9加入到緩衝區 5) 緩衝管理器用了數據4,而數據4以前已經在緩衝區存在了,全部數據3成了緩衝區最先使用的數據
6) 緩衝管理器用了數據1,緩衝區已滿,因而數據3被清除由於它是最先使用的數據,數據1 被添加到緩衝區中。 這算法能很好地工做,但也存在一些侷限性。若是在一個大表中進行全局搜索呢?換句話說,若是表/索引的大小比緩衝區還大會發生什麼事呢?使用這算法會把以前在緩衝中的值所有移走,可是全局掃描可能只會使用一次

改善一下

爲了防止上述的狀況,某些數據庫會添加特定的規則。若是根據Oracle 文檔所言

對於很大的表,數據庫會直接用路徑讀,這直接加載塊... 以免填滿緩衝區緩存。對於中等大小的表,數據庫可使用直接讀取或者是讀緩存。若是它決定要讀緩存,數據庫會把這塊放到 LRU列表的最後,來防止掃描有效地清除緩存區緩存

也有不少其餘的辦法像是一個LRU的高級版本叫 LRU-K。像 SQL Server 就用了 LRU-k 而 K = 2 這算法背後的思想是要考慮更多的歷史。使用簡單的 LRU(K=1時的LRU-K),算法只用考慮上次使用數據的時間。而LRU-K:

  • 它會考慮最後K次的數據使用狀況
  • 數據的使用次數會加入權重
  • 若是一堆新數據會被加載到緩存,則不會刪除常用的舊數據(由於他們的權重中更高)
  • 但若是數據再也不使用了,這算法也不會一直把數據保留到緩衝區
  • 因此隨着時間的推移, 一直沒用到的數據權重會一直遞減

而計算權重的成本是很大的,這就是 SQL Server 只用到 K = 2。這個值在可接受的成本範圍內性能不錯。 關於 LRU-K 的更深刻的學習,你能夠閱讀最原始的研究論文(1993):《用於數據庫磁盤緩衝與的LRU-K頁替換算法》

其餘算法

固然啦,還有不少其餘的用於管理緩存的算法,像是:

  • 2Q:(和 LRU-K 相似的算法)、
  • CLOCK算法 (和 LRU-K 相似的算法)
  • MRU (most recently use,最近最常使用,和 LRU 同樣都是使用邏輯但用不一樣的規則)
  • LRFU (最近且最常使用)

有些數據庫可能容許使用其餘的算法而不是默認算法

## 寫入緩衝區

我只講過在去緩衝區要在使用前先加載。但在數據庫中,有寫緩衝區的操做,這用來存儲數據,把數據串聯起來刷新磁盤數據。而不是逐個逐個地寫數據,產生不少的單次磁盤訪問。

請記住,buffer 存儲的是頁(page,數據的最小單元)而不是 row(邏輯上/人性化觀察數據的)。一個頁在緩衝池被修改但沒有寫入到磁盤是骯髒的。有不少算法能決定髒頁寫入磁盤的最佳時間,它和事務概念關係很密切,那是下一部分的內容。

事務管理器

最後,但也很重要,這部分會講事務管理器。咱們將看到進程是如何確保每一個查詢都在本身的事務中執行。在此以前,咱們須要明白事務的

I’m on acid(我酸了。。。)

一個ACID事務是一個工做單元,它要保證4個屬性:

  • 原子性(Atomicity) :即便持續10個小時,交易也是「所有或所有」。若是事務崩潰,則狀態返回到事務以前( 事務被回滾 )。
  • 隔離性(Isolation) : 若是2個事務A和B同時運行事務A和B的結果必須相同,無論A是否在事務B以前/以後/期間完成
  • 持久性(Durability) : 一旦事務被提交(即成功結束),不管發生什麼(崩潰或錯誤),數據都會保留在數據庫中。
  • 一致性(Consistency) : 只有合法的數據(關於關係約束和功能約束)能寫入數據庫,一致性與原子性和隔離性有關

在同一事務期間,您能夠運行多個SQL查詢來讀取,建立,更新和刪除數據。當兩個事務使用相同的數據時,開始混亂了。典型的例子是從帳戶A到帳戶B的匯款。想象一下,您有2筆事務:

  • 事務1(T1)從帳戶A取出100美圓給帳戶B
  • 事務2(T2)從帳戶A取出50美圓給帳戶B

若是咱們回到ACID屬性:

  • 原子性(Atomicity) :確保不管在T1期間發生什麼(服務器崩潰,網絡故障......),你最終都不能出現從A取走100元而B沒有收到錢的狀況(這種狀況是不一致的狀態)
  • 隔離性(Isolation): 確保若是T1和T2同時發生,最終A都會取出150元,而B都會獲得150元,而不是其餘結果。例如:A被取走150元,而B只獲得50元,由於T2清掉了T1的部分行爲(這也是狀態不一致)
  • 持久性(Durability) : 若是數據庫在 T1 提交後奔潰,持久性能確保了T1不會憑空消失
  • 一致性(Consistency) : 確保系統中不會(無端)建立或銷燬任何資金。

[若是你願意,能夠跳到下一部分,我要說的對於文章的其他部分並不重要]

許多現代數據庫不使用純隔離做爲默認行爲,由於它帶來了巨大的性能開銷。 SQL規範定義了4個級別的隔離:

  • 可序列化(Serializable,SQLite默認模式):最高級別的隔離。兩個同時發生的事務100%隔離,每一個事務有本身的「世界」。
  • 可重複讀取(MySQL中的默認行爲):除了一種狀況外,每一個事務都有本身的「世界」。 若是事務提交成功並添加新數據,則這些數據將在另外一個仍在運行的事務中可見。 可是,若是A修改數據提交成功,修改後的數據對正在運行的事務中不可見。 所以,事務之間的這種隔離中斷只涉及新數據,而不是現有數據。

例如,若是事務A執行 「TABLE_X中的SELECT count(1)」,而後由事務B在TABLE_X中添加並提交新數據,若是事務A再次執行count(1),則該值將不是相同。 這稱爲幽靈讀取(phantom read)

  • 讀取已提交(Oracle,PostgreSQL和SQL Server中的默認行爲):這是可重複讀+突破隔離性。若是事務A讀了數據D,而後這數據被修改(或者刪除),並被B提交了。若是A再次讀,事務A再次讀取數據D時就會看到被B修改的數據的改變(或者刪除部分) 這稱爲不可重複讀取。
  • 讀取未提交:最低級別的隔離。它是一個讀取已提交+一個新的隔離中斷。 若是事務A讀取數據D,而後該事務(未提交但仍在運行)修改了數據D,則若是A再次讀取數據D,則它將看到修改的值。 若是事務B被回滾,那麼第二次由A讀取的數據D沒有任何意義,由於它已經被事件B修改過,從未發生過(由於它被回滾)。 這稱爲髒讀

多數數據庫添加了本身的自定義的隔離級別(好比 PostgreSQL、Oracle、SQL Server的使用快照隔離),並且並無實現SQL規範裏的全部級別(尤爲是讀取未提交級別)。

默認的隔離級別能夠由用戶/開發者在創建鏈接時覆蓋(只須要增長很簡單的一行代碼)。

併發控制

確保隔離性,一致性和原子性的真正問題是對相同數據(添加,更新和刪除)的寫操做:

  • 若是全部事務僅讀取數據,則它們能夠同時工做,而無需修改另外一個事務的行爲。
  • 若是(至少)其中一個事務是修改其餘事務讀取的數據,則數據庫須要找到一種方法來隱藏其餘事務中的此修改。此外,它還須要確保不會被另外一個沒有查看修改數據的事務擦除此修改。

這種問題叫 併發控制

解決問題的最簡單的方式是每一個事務逐一運行(按順序)。但這根本就沒有伸縮性的,一個多進程/多核心的服務器上只有一個核,這太沒效率了

解決這個問題的方法是,每次建立或取消事務:

  • 監控全部事務的的操做
  • 檢查是否存在兩個或以上的事務存在衝突,由於他們正在讀/改相同的數據
  • 給衝突的事務從新編排來減小衝突部分的數量
  • 按必定的順序執行衝突的部分(同時非衝突的部分併發執行)
  • 要考慮事務有可能被取消

更正規地說,這是一個調度衝突的問題。更具體地講,這是個很是難的且CPU開銷大的優化的問題。企業級數據庫沒法負擔等待數小時,爲新的事務找尋最佳的調度。所以,他們用不太理想的方法,它會讓更多的時間花費在處理事務衝突上。

鎖管理

爲了解決這個問題,大部分數據庫使用 和/或 數據版本控制。因爲這是個大話題,我關注點會在鎖的部分,而後我會說一小點數據版本控制

悲觀鎖

這鎖背後的思想是:

  • 若是事務須要數據
  • 它就鎖住數據
  • 若是另外一個事務也須要數據
  • 它要等到第一個事務釋放數據

這種叫排他鎖(exclusive lock) 但對事務只是要讀取數據,使用排他鎖就很昂貴了。由於它強制讓那些只想讀一些數據的事務去等待。 這就是爲何會有另一種鎖,共享鎖(share lock) 共享鎖是這樣的:

  • 若是事務只須要讀數據A
  • 它會共享鎖定數據,並讀取數據
  • 若是第二個事務也要讀數據A
  • 它會共享鎖定數據,並讀取數據A
  • 若是第三個事務要修改數據A
  • 它「排除鎖定」數據,它必須等到其餘2個事務釋放其共享鎖,才能對數據A使用其排他鎖

可是,若是數據在用排它鎖,而事務只須要讀數據,也不得不等到排他鎖結束才能用共享鎖鎖住數據

鎖管理器是提供和釋放鎖的進程。在內部,它用哈希表(key是被鎖的數據)存儲了鎖,而且知道每一個數據

  • 那個事務鎖住了數據
  • 那個事務在等待數據

死鎖

可是使用鎖可能致使2個事務永遠等待數據的狀況:

在這圖中:

  • 事務A有一個排他鎖鎖住了數據1,要等待數據2
  • 事務B有一個排他鎖鎖住了數據2,要等待數據1

這叫作 死鎖 。 在死鎖中,鎖管理器選擇要取消(回滾)事務來刪除死鎖,這個決定也不太容易啊

  • 殺掉修改數據量最小的事務(這會產生最便宜的回滾)更好嗎?
  • 殺死另外一個最新的事務,由於舊的事務已經等待很長時間了,是否是更好?
  • 殺死能用更少時間結束的事務(避免可能的資源飢餓)?
  • 在回滾的狀況下,此回滾會影響多少個事務?

但在作出這個選擇以前,須要檢查是否存在死鎖。 哈希表能夠當作是一張圖表(像前面的那張圖)。若是圖中有個循環就會出現死鎖。因爲檢查循環(由於全部鎖的圖標是至關的大)是成本是很昂貴的,因此一個更簡單的方法會被常用:使用 時間超時(timeout) 。 若是在給定超時範圍內未能鎖定,就說明事務進入了死鎖狀態。 鎖管理器也能夠在加鎖以前檢查該鎖會不會變成死鎖,但要完美作到這點成本也是很昂貴的。所以這些預檢常常設置一些基本規則。

兩段鎖

確保純粹的隔離的 最簡單方式 是在事務開始的時候加鎖,在事務結束的時候釋放鎖。這意味着事務在開始前不得不等待它的全部鎖,而後爲事務持有鎖,當結束時釋放鎖。它能夠工做的,可是在等待全部鎖的時候回浪費不少的時間

一個更快的方法是 兩段鎖協議(由DB2和SQL Server使用),其中事務分爲兩個階段:

  • 成長階段:事務能夠得到鎖,但不能釋放鎖。
  • 收縮階段,事務能夠釋放鎖(對已經處理過的數據而且不會再次處理),但沒法得到新的鎖。

這兩條簡單規則背後的思想是:

  • 釋放再也不使用的鎖,以減小等待這些鎖的其餘事務的等待時間
  • 防止事務在事務開始後被修改數據的狀況,所以與事務獲取的第一個數據不一致。

這協議能很好地工做,除非是那個事務修改後的數據並釋放鎖後,事務被取消或者回滾了。你可能遇到一種狀況是,一個事務讀了另外一個事務修改後的值,而這個事務要被回滾的。要避免此問題,必須在事務結束時釋放全部獨佔鎖。

說多幾句

固然,真正的數據庫會用更復雜的系統,涉及更多類型的鎖(如意向鎖 intention lock )和更多粒度(行級鎖,頁級鎖,分區鎖,表鎖,表空間鎖)可是這個道理都是同樣的。 我只探討純粹基於鎖的方法,數據版本控制是解決這個問題的另外一個方法。 版本控制背後的思想是:

  • 每一個事務均可以同時修改相同的數據
  • 每一個事務都有本身的數據副本(或版本)
  • 若是2個事務修改相同的數據,則只接受一個修改,另外一個將被拒絕,相關的事務將被回滾(而且可能從新運行)。

它提升了性能,由於:

  • 讀事務不會阻塞寫事務
  • 寫事務不會阻塞讀
  • 沒有『臃腫緩慢』的鎖管理器帶來的額外開銷

一切都比鎖更好,除了兩個事務寫入相同的數據(由於總有一個被回滾)。只是,你的磁盤空間會被快速增大。

數據版本控制和鎖定是兩種不一樣的簡介:樂觀鎖定與悲觀鎖定。他們都有利有弊;它實際上取決於應用場景(更多讀取與更多寫入)。有關數據版本控制的演示文稿,我推薦這篇關於PostgreSQL如何實現多版本併發控制,是很是好的演示文稿

某些數據庫(如DB2(直到DB2 9.7)和SQL Server(快照隔離除外))僅使用鎖。其餘像PostgreSQL,MySQL和Oracle使用涉及鎖和數據版本控制的混合方法。我不知道只使用數據版本控制的數據庫(若是您知道基於純數據版本的數據庫,請隨時告訴我)。

[2015年8月20日更新]讀者告訴我: Firebird和Interbase使用沒有鎖的版本控制。 版本控制對索引有一個有趣的影響:有時一個惟一索引包含重複項,索引能夠有比表有行更多的條目,等等。

若是你在不一樣的隔離級別上讀過那部分,你會發現增長隔離級別時,會增長鎖的數量,從而增長事務等待鎖定所浪費的時間。這就是大多數數據庫默認狀況下不使用最高隔離級別(Serializable)的緣由。

與往常同樣,您能夠本身檢查主數據庫的文檔(例如 MySQLPostgreSQLOracle)。

日誌管理

咱們已經看到,爲了提升性能,數據庫將數據存儲在內存緩衝區中。可是若是服務器在提交事務時崩潰,那麼在崩潰期間你將丟失在內存中的數據,這會破壞事務的持久性。

你能夠在磁盤上寫入全部內容,但若是服務器崩潰,你最終會將數據可能只有部分寫入磁盤,這會破壞事務的原子性。

任何事務的修改都只有撤銷和已完成兩個狀態 要解決這個問題,

有兩種方法:

  • 影子副本/頁面(Shadow copies/pages):每一個事務都建立本身的數據庫副本(或只是數據庫的部分數據)並在此副本上工做。若是出現錯誤,則刪除副本。若是成功,數據庫會當即使用文件系統技巧切換副本中的數據,而後刪除「舊」數據。
  • 事務日誌:事務日誌是一個存儲空間。在每次磁盤寫入以前,數據庫會在事務日誌中寫入信息,以便在事務崩潰/取消的狀況下,數據庫知道如何刪除(或完成)未完成的事務。

WAL Write-Ahead Logging protocol (預寫日誌記錄協議)

在涉及許多事務的大型數據庫上使用時,影子副本/頁面會產生巨大的磁盤開銷。 這就是現代數據庫使用事務日誌的緣由。事務日誌必須存儲在穩定的存儲中。我不會深刻研究存儲技術,但必須使用(至少)RAID磁盤來防止磁盤故障。

大多數數據庫(至少Oracle,SQL Server,[DB2PostgreSQL,MySQL和 SQLite)使用Write-Ahead Logging協議(WAL)處理事務日誌。

WAL協議是一組3條規則:
1)數據庫的每次修改都會生成一條日誌記錄,而且 必須在將數據寫入磁盤以前將日誌記錄寫入事務日誌。
2)日誌記錄必須按順序寫入;日誌記錄A在日誌記錄B以前發生就必須在B以前寫入
3)提交事務時,必須在事務成功結束以前,在事務日誌中寫入提交順序。

這個工做由日誌管理器完成。一種簡單的方法是在緩存管理器和數據訪問管理器(在磁盤上寫入數據)之間,日誌管理器在將事務日誌寫入磁盤以前將每一個更新/刪除/建立/提交/回滾寫入事務日誌。容易,對嗎? 錯誤的答案!都講了這麼多了,你應該知道與數據庫相關的全部內容都受到「數據庫效應」的詛咒。認真地是,問題是找到一種在保持良好性能的同時編寫日誌的方法。若是事務日誌上的寫入速度太慢,則會下降全部內容的速度。

ARIES

1992年,IBM研究人員「發明了」一種名爲ARIES的WAL加強版。ARIES或多或少地被大多數現代數據庫使用。邏輯可能不同,但ARIES背後的理念隨處可見。我給發明加了引號是由於,是由於根據麻省理工學院的這門課程,IBM的研究人員「只不過是編寫事務恢復的良好實踐」。自從我5歲時ARIES論文發表以來,我並不關心來自辛酸研究者的這個古老八卦。事實上,在咱們開始這個最後的技術部分以前,我只是把這些信息給你一個休息時間。 我已經閱讀了關於ARIES的大量研究論文,我發現它很是有趣!在這部分中,我將僅向你概述ARIES,但若是你須要真正的知識,我強烈建議您閱讀本文。 ARIES 表示的是恢復和利用語義隔離算法(Algorithms for Recovery and Isolation Exploiting Semantics)。 這項技術的目的是有兩個的:
1) 寫日誌時有良好的性能
2) 有快速可靠的恢復 數據庫必須回滾事務有多種緣由:

  • 用戶取消了它
  • 因爲服務器或網絡故障
  • 由於事務已經破壞了數據庫的完整性(例如,您對一個列有一個惟一性約束,事務添加了個重複值)。
  • 因爲死鎖

有時候(好比網絡出現故障),數據庫能夠恢復事務。 怎麼可能?要回答這個問題,咱們須要瞭解日誌記錄中的存儲的信息。

日誌

事務期間的每一個 *操做(添加/刪除/修改)都會生成一個日誌* 。該日誌記錄包括:

  • LSN: 惟一的日誌序列號(Log Sequence Number)。LSN按時間順序給出。操做A在操做B以前發生,日誌A的LSN會比日誌B的LSN低。
  • TransID:產生操做的事務ID。
  • PageID:修改數據的磁盤位置。磁盤上的最小數據量是一個頁(Page),所以數據的位置就是包含在數據的頁的位置。
  • PrevLSN:指向同一事務生成的上一條日誌記錄的連接。
  • UNDO:取消本次操做的方法。

好比,若是操做是更新,UNDO將會回到元素更新前的值或狀態(物理UNDO),或者回到原來狀態的反向狀態(邏輯UNDO)

  • REDO:重複本次操做的方法。一樣有2種方法: 保存操做後的元素值/狀態,或者保存操做自己以便重複。
  • …:(供你參考,一個 ARIES 日誌還有 2 個字段:UndoNxtLSN 和 Type)。

此外,磁盤上的每一個頁面(存儲數據,而不是日誌)具備修改數據的最後一個操做的日誌記錄(LSN)的id。

給出LSN的方式更復雜,由於它與日誌的存儲方式有關。但這個背後的思想仍然是同樣的。 ARIES僅使用邏輯UNDO,由於處理物理UNDO真是一團糟。

注意:據我所知,只有PostgreSQL沒有使用UNDO。它使用垃圾收集器守護程序來刪除舊版本的數據。這與PostgreSQL中數據版本控制的實現有關。

爲了更好地說明這點,這裏是查詢「UPDATE FROM PERSON SET AGE = 18;」生成的日誌記錄的可視化和簡化示例。假設此查詢在事務18中執行。

每一個日誌都有一個惟一的LSN。鏈接的日誌屬於同一事務。日誌按時間順序連接(連接列表的最後一個日誌是最後一個操做的日誌)。

日誌緩衝區

爲避免日誌寫入成爲主要瓶頸,使用 日誌緩衝區

當查詢執行程序要求修改時:

1) 緩存管理器將修改存儲其緩衝區中
2) 日誌管理器將關聯的日誌存儲在其緩衝區中
3) 到了這一步,查詢執行器認爲操做完成了(所以能夠請求作另外一次修改);
4)而後(稍後)日誌管理器將日誌寫入事務日誌。什麼時候寫日誌的決定是由算法完成的。
5)而後(稍後)緩存管理器將修改寫入磁盤。什麼時候在磁盤上寫入數據是由算法完成的。 當事務被提交,這意味着對於事務中的每一個操做,步驟1,2,3,4,5也作完了。 在事務日誌中寫入很快,由於它只是「在事務日誌中的某處添加日誌」,而在磁盤上寫入數據則更復雜,由於它要 「以能快速讀取數據的方式寫入數據」。

STEAL 和 FORCE 策略

出於性能緣由,步驟5可能在提交以後完成 ,由於在崩潰的狀況下,仍然可使用REDO日誌恢復事務。這稱爲NO-FORCE政策 。 數據庫能夠選擇FORCE策略(即必須在提交以前完成步驟5)以下降恢復期間的工做負載。 另外一個問題是選擇是否在磁盤上逐步寫入數據(STEAL策略),或者緩衝區管理器是否須要等到提交順序一次寫入全部內容(NO-STEAL)。STEAL和NO-STEAL之間的選擇取決於您的需求:使用UNDO日誌快速寫入長時間恢復或快速恢復? 如下是這些影響恢復策略摘要:

  • STEAL / NO-FORCE須要UNDO和REDO:性能高,但日誌和恢復過程(如ARIES)更復雜。這是大多數數據庫的選擇。

注意:我在多篇研究論文和課程中讀到了這個事實,但我沒有(明確地)在官方文件中找到它。

  • STEAL/ FORCE 只須要 UNDO
  • NO-STEAL/NO-FORCE 只須要 REDO.
  • NO-STEAL/FORCE 什麼也不須要: 性能最差,並且須要巨大的內存。

關於恢復

好的,咱們有很好的日誌,讓咱們使用它們! 假設新實習生讓數據庫崩潰了(規則1:永遠是實習生的錯誤)。你重啓數據庫並開始恢復進程 ARIES在3個關卡中讓崩潰恢復過來:

1) 分析關卡:恢復進程讀所有的事務日誌去建立奔潰期間發生的事情的時間線。它會肯定哪些事務要回滾(全部事務沒有提交的都會回滾)和哪些在奔潰期間的數據須要寫入到磁盤
2) redo關卡:這關從分析期間肯定一條日誌記錄開始,並使用 REDO 來將數據庫更新到崩潰以前的狀態。

在 REDO 階段,REDO日誌按時間順序處理(使用LSN)。 對於每一個日誌,恢復過程將讀取包含要修改數據的磁盤每頁上的 LSN 若是 LSN(磁盤的頁)>= LSN(日誌記錄),則代表數據在奔潰以前已經寫入磁盤了(值已經被日誌以後、奔潰以前的某個操做覆蓋)因此不用作什麼 若是LSN(磁盤的頁)< LSN(日誌記錄),那麼磁盤上的頁將被更新。 即便對於要回滾的事務,重作也會完成,由於它簡化了恢復過程(但我確信現代數據庫不會這樣作)。

3) undo關卡: 此過程將回滾崩潰時未完成的全部事務。回滾從每一個事務的最後日誌開始,並按照反時間順序處理UNDO日誌(使用日誌記錄的PrevLSN)。

在恢復期間,事務日誌必須留意中恢復過程的操做,以便寫入磁盤上的數據與事務日誌中寫入的數據同步。解決方案多是移除被 undone的事務日誌記錄,這是很困難的。相反,ARIES在事務日誌中寫入補償日誌,邏輯上刪除被取消的事務日誌記錄。 當事務被手動取消,或者被鎖管理器取消(爲了消除死鎖),或僅僅由於網絡故障而取消,那麼分析階段就不須要了。實際上,有關 REDO 和 UNDO 的信息在 2 個內存表中:

  • 事務表(保存當前全部事務的狀態)
  • 髒頁表(保存哪些數據須要寫入磁盤)

當新的事務產生時,這兩個表由緩存管理器和事務管理器更新。由於它們是在內存中,當數據庫崩潰時它們也被破壞掉了。 分析階段的任務就是在崩潰以後,用事務日誌中的信息重建上述的兩個表。爲了加快分析階段,ARIES提出了一個概念:檢查點(check point),就是不時地把事務表和髒頁表的內容,還有此時最後一條LSN寫入磁盤。那麼在分析階段當中,只須要分析這個LSN以後的日誌便可。

相關文章
相關標籤/搜索