SQLite事務管理

事務管理對數據庫一致性是相當重要的。數據庫實現ACID屬性以確保一致性。SQLite依賴於本地文件鎖和頁日誌來實現ACID屬性。SQLite只支持扁平事務,並不支持事務嵌套和保存點能力。算法

1.1 事務類型sql

SQLite執行在一個事務中的每條語句,支持讀事務和寫事務。應用程序只能是在讀或寫事務中才能從數據庫中讀數據。應用程序只能在寫事務中才能向數據庫中寫數據。應用程序不須要明確告訴SQLite去執行事務中的單個SQL語句,SQLite時自動這樣作的,這是默認行爲,這樣的系統叫作自動提交模式。這些事務被叫作自動事務,或系統級事務。對於一個select語句,SQLite創建一個讀事務,對於一個非select語句,SQLite先創建一個讀事務,再把它轉換成寫事務。每一個事務都在語句執行結束時被提交或被終止。應用程序不知道有系統事務,應用程序只是把SQL語句提交給SQLite,由SQLite再去處理關於ACID的屬性,應用程序接收從SQLite執行SQL返回的結果。一個應用程序能夠在相同的數據庫鏈接上引起執行select語句(讀事務)的並行執行,可是隻能在一個空閒鏈接上引發一個非select語句(寫事務)的執行。自動提交模式可能對某些應用程序來說代價太高,尤爲是那些寫密集的應用程序,由於對於每個非select語句,SQLite須要重複打開,寫入,關閉日誌文件。在自動提交模式中,在每條語句執行的最後,SQLite丟棄頁緩衝。每條語句執行都會從新構造緩衝區,從新構造緩衝區是花費大,低效的行動,由於會調用磁盤I/O。而且,存在併發控制的開銷,由於對每一句SQL語句的執行,應用程序須要從新獲取和釋放對數據庫文件的鎖。這種開銷會使性能顯著降低(尤爲是大型應用程序),並只能以打開一個包括了不少SQL語句的用戶級別的事務來減輕這種狀況(如:打開多個數據庫)。應用程序能夠用begin命令手動的開始一個新的事務,這種事務被稱爲用戶級事務(或用戶事務)。當begin執行後,SQLite離開默認的自動提交模式,在語句結束時不會調用commit或abort。也不會丟棄該頁的緩衝。連續的SQL語句是整個事物的一部分,當應用程序執行commit/respectively/rollback指令時,SQLite提交或分別或停止事務。若是當事務停止或失敗,或應用程序關閉鏈接,則整個事務回滾。SQLite在事務完成時恢復到自動提交模式上來。
數據庫

 SQLite只支持扁平事務。一個應用程序不能一次性在一個數據庫鏈接中打開多個用戶事務。若是你在用戶事務中執行begin命令,返回一個錯誤。在事務中,每一個非select語句都被以一個單獨的語句級的子事務執行。雖然SQLite沒有通常的保存點能力,但它能爲當前語句子事務確保保存點。若是當前的語句執行失敗,SQLite不會退出用戶事務,它會從新裝載數據庫到語句開始執行以前的狀態,事務從那裏再繼續。失敗的語句不會改變以前的SQL語句的執行結果,除非主用戶事務本身停止。SQLite能很好的幫助一個長的用戶事務承受其中一些語句的失敗。windows

BEGIN;
INSERT INTO tablel values(100);
INSERT INTO table2 values(20, 100);
UPDATE tablel SET x=x+1 WHERE y> 10;
INSERT INTO table3 VALUES (1,2,3);
COMMIT;緩存

在上邊的例子中,四個語句中的每一個都被看成一個單獨執行的事務,依次執行。若是在更新操做的第十行出現了約束錯,則更新操做的前九行將會回滾,可是其餘三個插入操做的更改將會在執行commit命令時被提交。安全

 

1.2 鎖管理數據結構

SQLite爲了序列化的執行事務,採用鎖機制來調節來自事務的訪問數據庫的請求。嚴格遵循兩段鎖協議,如:只在事務結束時釋放鎖。SQLite只有數據庫級別的鎖,沒有行級頁級表級的鎖,即對整個數據庫文件加鎖,而不是對文件中部分數據項加鎖。多線程

子事務經過用戶事務的容器得到鎖。用戶事務持有全部的鎖,直到用戶事務的提交或停止,與子事務的結果無關。併發

1.2.1 鎖類型及其兼容性dom

從單一的事務類型來看,一個數據庫文件能夠是如下五種鎖狀態之一:

未加鎖(NOLOCK):事務不持有對數據庫文件的鎖,事務不能對數據庫文件讀和寫。若是其餘的事務所擁有的鎖狀態容許的話,這些事務就能對數據庫進行讀寫。當一個事務啓動了數據庫文件時,未加鎖是默認狀態。

共享鎖(SHARED):此鎖只容許讀數據庫文件。任意數量的事務均可以同時在同一文件上持有此鎖,所以能夠有任意多的併發讀事務。當一個或多個事務在文件上持有共享鎖時,別的事務不容許寫數據庫文件。

保留鎖(RESERVED):此鎖容許從數據庫文件中讀取。保留鎖指一個事務在未來某些時間點要寫數據庫文件,可是當前對數據庫只是讀操做。在一個文件上最多隻能有一個保留鎖,可是保留鎖能夠與任何數量的共享鎖共存。其餘的事務可能會得到此文件的共享鎖,但不能是其餘類型的鎖。

未決鎖(PENDING):此鎖容許讀書據庫文件。一個未決鎖表示事務想盡快的寫入數據庫文件。此鎖等待當前全部的共享鎖清除以獲取一個排他鎖。在文件上最多隻能有一個未決鎖,可是與共享鎖的區別是:其餘事務能持有已得到的鎖,可是不能在得到新的鎖(共享鎖或其餘鎖)。

排他鎖/獨佔鎖(EXCLUSIVE):這是唯一的容許寫數據庫文件的鎖(也容許讀)。在文件上只容許有唯一的排他鎖,不能與其餘類型的排他鎖共存。

矩陣以下:

  (新申請)共享鎖 (新申請)保留鎖 (新申請)未決鎖 (新申請)排他鎖
(已有)共享鎖 Y Y Y N
(已有)保留鎖 Y N N N
(已有)未決鎖 N N N N
(已有)排他鎖 N N N N

像SQLite這樣的數據庫系統至少須要排他鎖,其餘的鎖的模式只是增長事物的併發性,只有使用了排他鎖,事務才能串行的執行事務。有了共享鎖和排他鎖,系統能同時併發的執行不少讀事務。在實踐中,事務在共享鎖下讀取一個數據元素,修改這個元素,申請一個排他鎖再把數據元素寫回到文件中。若是兩個事務同時這樣作的話可能會產生死鎖,在死鎖中事務執行不能取得進展。保留鎖和未決鎖都是爲了減小這些死鎖而設計。這兩種鎖也幫忙改善了所謂的讀者寫着問題(讀者數量永遠大於寫者數量)。

1.2.2 鎖獲取協議

在讀取數據庫文件的第一頁(任何一頁)以前,事務獲取一個共享鎖表示它打算從文件中讀取頁。在修改數據庫中的任何一頁以前,事務獲取一個保留鎖表示它打算在不久的未來寫。得到了保留鎖,事務就能在緩衝頁內進行修改。在寫入數據庫前,事務須要得到對數據庫的排他鎖。普通的鎖事務是從無鎖->共享鎖->保留鎖->未決鎖->排他鎖。共享鎖到未決鎖的直接轉化只能是由於日誌文件須要回滾。可是若是這樣就沒有其餘的事務會從共享鎖轉化到保留鎖。未決鎖是一箇中間鎖,在鎖管理器外是不可見的:尤爲是頁面管理器不會要求鎖管理器獲得一個未決鎖,通常會要求一個排他鎖。所以未決鎖只是爲得到排他鎖的一箇中間步驟。爲了防止在已得到未決鎖後得到排他鎖失敗,則頁面管理器會執行後續的請求來升級未決鎖爲排他鎖。

雖然鎖解決了併發控制問題,可是引入一個新問題。假設兩個事務都對一個文件持有共享鎖,他們都請求保留鎖。其中一個獲得了保留鎖,另外一個等待。過了一會,持有保留鎖的事務要請求排他鎖,等待另外一個事務清除共享鎖。可是共享鎖將永遠不會清除由於那個持有共享鎖的事務一直在等待。這種狀況叫作死鎖。有兩種方法來對付死鎖:1.預防 2.檢測和破壞。SQLite避免死鎖的造成,SQLite老是在非阻塞狀態下獲取文件鎖。若是其不能表明一個事務獲取鎖,將會重試有限次數(重試次數將會由程序在運行時預設,默認次數是一次)。若是全部的重試失敗的話,將會返回SQLITE_BUSY錯誤代碼給應用程序。應用程序會後退並稍後重試或者停止事務。所以,不存在着造成死鎖的可能性。然而至少在理論上,事務沒有成功得到鎖會致使飢餓。可是SQLite不是應用在企業級的高併發程序中,飢餓就不是個大問題。

1.2.3 鎖實現

SQLite用本地操做系統的文件鎖函數實現SQLite本身的鎖系統(鎖實現是平臺相關的,由於不一樣系統由不一樣的API)。Linux實現了兩個鎖模式,讀所和寫鎖, 來鎖定文件相鄰區域。爲了不混淆,用讀鎖和寫鎖分別表明共享鎖和排他鎖。Linux把鎖分配給進程(單線程應用程序),或線程(多線程應用程序)。許多線程均可以在同一文件區域上持有讀鎖,可是隻能有一個線程能夠在一個文件區域上持有寫鎖。寫鎖是排他的,不管讀鎖仍是寫鎖。讀鎖和寫鎖能夠在一個文件上共存,可是是在不一樣的區域。一個線程只能在一個區域持有一種鎖,若是在一個已經加鎖的區域應用一個新的鎖,則現有的鎖會轉換成新的鎖模式。SQLite用本地操做系統在不一樣文件區域的鎖模式實現了SQLite本身的四種鎖模式(這是經過fcntl系統調用實現設置和釋放本地區域的鎖)。

在指定範圍的字節上設置一個讀鎖來得到一個共享鎖。

在特定範圍的全部字節上設置一個寫鎖來得到一個排他鎖。

在共享範圍的外部的一個字節上設置一個寫鎖來得到一個保留鎖,這個字節叫作保留鎖字節。

在共享範圍的外部的不一樣於保留鎖字節的另外一個字節來設定一個寫鎖來得到未決鎖。

SQLite在共享區域保留510個字節(區域大小在文件頭處以名爲SHARED_SIZE的宏定義)。區域從SHARED_FIRST開始。

PENDING_BYTE宏(在0X40000000處定義,這是在過1G的第一個字節處定義鎖字節的開始)。

緊挨着PENDING_BYTE宏的下一個字節定義了PENDING_BYTE宏,緊挨着PENDING_BYTE的下一個字節定義了SHARED_FIRST

全部的鎖定字節都要裝進到一個單獨的數據庫頁中,即便最小的頁大小是512字節的。

(1+1+510=512)即(PENDING_BYTE+RESERVED_BYTE+SHARED_SIZE=512)。

在windows上,鎖是強制性的,即鎖對全部進程都是強制性的,不管進程之間是否有合做。鎖定的空間是由操做系統保留的。所以,SQLite不能在被鎖定的空間內存儲實際的數據。所以頁面管理器不會分配涉及到鎖定的頁面,這個頁面也不會應用到其餘平臺上去,也不會被跨平臺的其餘數據庫所使用。PENDING_BYTE在這麼高的地址是由於這樣SQLite就不會分配一個無用的頁,除非是一個很大的數據庫。爲了得到對數據庫文件的共享鎖,線程首先會對PENDING_BYTE得到一個本地的讀鎖來確保沒有其它的進程/線程在文件上有一個未決鎖。若是成功了,從SHARED_FIRST開始的SHARED_SIZE範圍的字節就會被讀鎖定,而後,在PENDING_BYTE上的讀鎖被釋放。

某些版本的windows只支持寫鎖,所以爲了得到對一個文件的共享鎖,一個單獨的在範圍外的字節會被寫鎖定,這個字節是被隨機選取的,因此這兩個獨立的讀者能同時訪問文件,除非他們不幸的的選擇了一樣的字節來設置寫鎖。在這樣的系統中,讀併發會被共享區域的規模大小所限制。線程在得到共享鎖以後只能得到保留鎖。爲了得到一個保留鎖,首先要對RESERVED_BYTE加寫鎖,同時線程不釋放它對文件的共享鎖(這樣確保了其餘線程不能得到文件的排他鎖)。

一個線程在得到未決鎖的過程當中不會得到保留鎖,這個屬性用來在SQLite在系統崩潰後回滾一個日誌文件。若是線程從共享鎖->保留鎖->未決鎖,在加未決鎖時不會釋放那兩個鎖。線程只在得到了未決鎖後才能得到排他鎖。爲了得到排他鎖,會在整個共享空間上加一個寫鎖。由於其它線程至少須要一個字節的讀鎖,因此排他鎖能夠肯定沒有其它的鎖會被得到。

1.3 日誌管理

日誌就是當事務或子事務語句停止時,被用於恢復數據庫的信息庫,也用於應用程序崩潰,系統崩潰,掉電。SQLite爲每一個數據庫維護一個日誌文件(內存數據庫沒有日誌文件)。只確保事務的回滾(undo而非redo),日誌文件常常被叫作回滾日誌。日誌文件常見於數據庫文件相同的目錄,並有相同的文件名,可是有-journal尾部。SQLite只容許在一個數據庫文件上最多有一個寫事務。每次寫事務都即時的創建日誌文件,而且當事務結束時刪除文件。

1.3.1 日誌結構

SQLite把回滾日誌記錄劃分到可變大小的日誌段中。每一個段以一個段頭開始,後接一個或多個日誌記錄。段頭的格式是:

magic number:8字節,0xD9, 0xD5, 0x05, 0xF9, 0x20, 0xA1, 0x63, and 0xD7。只用來作全面的檢查,無特別意義。

number of records:4字節,用於記錄在這個日誌段中有多少個有效的記錄數。

random number:4字節,用於估計各個日誌記錄的校驗和。不一樣的日誌段可能有不一樣的隨機數。

initial database page count:4字節,表示當前事務開始時有多少頁在文件中。

sector size:表示日誌所在的磁盤扇區大小。

unused space:表示是頭中未被使用的留做填充區間。

SQLite支持異步事務處理比普通的事務快。SQLite不建議使用異步事務,可是能夠經過執行SQLite的編譯命令來設置異步模式,這種模式經常使用於開發階段來減小開發時間。對於一些不測試從失敗恢復的測試程序也有好的效果。異步事務既不刷新日誌,也不刷新數據庫文件。日誌文件將有唯一的日誌段,日誌段數是-1(0XFFFFFFFF),實際值是文件的大小。回滾日誌文件一般包括一個日誌段。可是在某些狀況下,是個多段文件,SQLite屢次寫段頭記錄。(在刷新緩衝區章)每次寫頭記錄,都在扇區邊界寫。在一個多段的日誌文件中,number of recordes字段不會是0XFFFFFFFF。

1.3.2 日誌記錄結構

從當前寫事務的非select語句產生日誌記錄。SQLite在頁級用舊值記錄技術。在對任何頁作第一次修改以前,該頁的原始內容(連同其頁號)都被做爲一個記錄寫入日誌文件。記錄中也包括了一個32位的校驗和。校驗和涵蓋了頁號和頁面的內容。出如今日誌段頭的32位隨機值用來作校驗密鑰。隨機值很重要,由於出如今日誌文件尾的垃圾數據多是其餘文件的數據,可是這個文件如今已經被刪除了。若是垃圾數據來自於一個過期的日誌文件,校驗和多是正確的。可是經過初始化校驗和與隨機值,不一樣的日誌文件對應不一樣的值。SQLite最小化該風險。

1.3.3 多數據庫事務日誌

一個應用程序能夠用執行SQLite的attach命令,在一個打開的鏈接上附加額外的數據庫。若是一個事務修改了多個數據庫,則每一個數據庫有其本身的回滾日誌。他們是獨立的回滾日誌,互相不知道對方的存在。爲了解決這個問題,SQLite額外的維護了一個單獨的聚合日誌叫作主日誌。主日誌不包含任何用於回滾目的的日誌記錄,可是,主日誌包含了每一個參與事務的單獨的回滾的日誌的名稱。每一個單獨的回滾日誌頁包含了主日誌的名字。若是沒有附加數據庫,或者沒有正參與當前用於更新事務的附加數據庫,則主日誌就不會被建立,普通的回滾日誌也不會包含任何關於主日誌的信息。主日誌通常位於主數據庫文件相同的目錄,有相同的名字,可是付以-mj,後跟八位的隨機數字字母串。主日誌也是臨時性的文件,當事務試圖提交時建立,當提交處理完成時刪除。

1.3.4 聲明日誌

當在一個用戶事務中,SQLite爲最新執行的非select語句維護了一個語句子日誌。日誌要求從語句執行失敗恢復數據庫。語句日誌是獨立的,普通的回滾日誌文件,是被任意命名的臨時文件(以sqlite_開頭)。文件不是爲崩潰恢復所須要的,而是爲語句停止所須要的。當語句執行完後,SQLite刪除文件。日誌沒有段頭記錄,number of log records值保存在一個內存中的數據結構中,因此數據庫文件大小存在語句開始的位置。這些日誌記錄沒有校驗和。

1.3.5 日誌協議

SQLite遵循預寫日誌協議(WAL),以保證數據庫變化的耐用性,和應用程序,系統,電源故障發生時數據庫的可恢復性。把日誌記錄寫到磁盤上是懶惰的,SQLite不強制馬上寫到磁盤上。然而,在把下一頁寫到磁盤上以前,會強制把全部的日誌記錄寫到磁盤上,這叫作刷新日誌。刷新日誌確保被寫入到日誌文件的全部的頁都被寫到了磁盤上。直到把日誌寫到磁盤上之前,修改數據庫文件都是不安全的。若是數據庫在日誌刷新到磁盤上之前就被修改了,發生了斷電,未刷新的日誌記錄將會被丟失,SQLite將不能徹底的回滾事務對數據庫的影響,結果是數據庫損壞。

1.3.6 提交協議

默認的提交邏輯是:提交強制日誌,提交強制數據庫。當應用程序提交事務時,SQLite確保在回滾日誌中的全部記錄都要被寫到磁盤上。在提交末尾,回滾日誌被刪除,事務完成。若是在此以前系統崩潰,事務提交會失敗,當數據庫下次被讀取時,會發生回滾。然而,在刪除回滾日誌文件前,全部對數據庫的改變都會被寫回磁盤。這樣作確保數據庫收到從事務開始到日誌文件被刪除以前的全部更新。

SQLite在提交異步事務時不執行數據庫和日誌文件的的刷新。所以,當發生故障時,數據庫可能被破壞,異步事務是會產生警告的。

 

1.4 事務性操做

像其餘DBMS同樣,SQLite的事務管理包括兩部分,正常處理,恢復處理。在正常處理中,頁面管理器在日誌文件中保存恢復信息,若是須要的話會在恢復處理期使用保存的信息。正常處理涉及了從數據庫文件中讀取頁,向數據庫文件中寫入頁,提交事務和子事務。此外,頁面管理器把刷新頁面緩衝區做爲正常處理的工做。大多數事務和語句子事務本身提交。可是有時候,一些事務和子事務停止本身。更罕見的,有應用程序或系統錯誤。在任一狀況下,SQLite都須要經過執行一些回滾操做來把數據庫恢復成一個可接受的一致性狀態。在語句和事務停止的狀況下,內存中的可靠信息對恢復是有用的。在崩潰的狀況下,可能會損壞數據庫,由於沒有內存信息。

1.4.1 讀操做

爲了操做數據庫頁,客戶端B+樹模塊須要以頁號爲參數使用sqlite3_get函數。客戶端須要調用這個函數,即便頁不在數據庫文件中:頁會被頁管理器建立。若是一個共享鎖或更強的鎖尚未被加到文件上的話,函數會得到文件的一個共享鎖。若是獲取共享鎖失敗,那麼有其餘事務正持有不兼容的鎖,會返回給調用者一個SQLITE_BUSY的錯誤代碼。不然,會執行一個緩衝區讀操做,並給調用者返回頁面。緩衝區讀操做會釘住頁面。在頁面管理器首次得到對一個數據庫文件的共享鎖時,對調用者來講已經開始了對文件的隱式的讀事務。在這點上,會決定文件是否須要恢復。若是文件確實須要恢復,頁面管理器會在把頁面返回給調用者以前執行恢復。

1.4.2 寫操做

在修改一個頁面以前,客戶端B+樹模塊必定要已經釘住該頁面(調用sqlite3pager_get)。對頁面調用sqlite3pager_write函數使得此頁面是可寫的。第一次在任何頁面上調用sqlite3pager_write函數,頁管理器須要得到對數據庫文件的保留鎖。若是頁面管理器不能得到該鎖,意味着有其餘事務得到了保留鎖或更強的鎖。在這種狀況下,寫入失敗,頁面管理器返回給調用者SQLITE_BUSY。頁面管理器第一次獲取一個保留鎖時,即讀事務升級成寫事務。在此時,頁面管理器創建並代開回滾日誌。初始化第一段的頭記錄,在記錄上記錄了原始的數據庫文件的大小,並把記錄寫入日誌文件。

爲了使一個頁面可寫,頁面管理器寫把頁面的原始內容寫入一個新的日誌記錄並寫到回滾日誌中。一旦頁面變成可寫的,客戶端就能夠不通知頁面管理器任意次數的寫頁面。對頁面的更改不會被馬上的寫回數據庫文件。

一旦一個頁面的鏡像被拷貝到回滾日誌中去,這一頁就不會出如今新的日誌記錄中,即便當前的事務在此頁上屢次調用寫函數。這種日誌的一個很好的屬性就是也能從日誌中被拷貝並被從新裝載回去。此外,撤銷操做(undo)是等冪的,所以撤銷操做是不產生任何補償日誌記錄的。SQLite從不在日誌中保存一個新的頁(即增長,例如附加以當前事務附加到數據庫上),由於頁中沒有舊的值。可是,日誌文件在被建立的時候,就在日誌段頭記錄指出數據庫文件的初始大小。若是數據庫文件在事務中膨脹的話,文件將會以回滾的形式被截斷到原始的尺寸。

1.4.3 緩存刷新

刷新緩存是頁面管理器的內部操做,客戶端不可能直接調用刷新緩存。最終,頁面管理器想把一些已經修改的頁面寫回到數據庫文件,或者是由於緩衝區已滿,須要進行緩衝區替換,或者是由於事務準備提交修改。頁面管理器執行以下步驟:

1. 肯定是否須要刷新日誌文件。若是事務是異步的,而且已經向日志文件中寫入了新數據,而且數據庫不是一個臨時文件(若是是一個臨時數據庫,咱們不關心在電源故障後是否能回滾,因此沒有日誌刷新),那麼頁面管理器就決定作一第二天志刷新。在這種狀況下,會作一個對日誌文件的fsync系統調用,確保至今的寫的全部的日誌記錄都已經在磁盤上了(而不是在操做系統空間或在磁盤控制器的內部緩存中)。此時,頁面管理器不在當前日誌段頭寫number of log records值(此值是回滾文件的重要資源,當段頭被格式化,值由於同步記錄被設成0,由於異步被設成0XFFFFFFFF)。在日誌被刷新後,頁面管理器把值寫入當前的日誌段頭,並再次刷新到文件中(日誌文件被刷新兩次,第二次刷新致使存儲值的磁盤塊被重寫。若是假設重寫是原子的,那麼能保證在刷新這個環節上日誌不會被破壞。不然,會存在一些小的風險)。由於磁盤操做不是原子的,也將不會重寫值。會爲新來的日誌記錄,建立一個新的日誌段。在此狀況下,SQLite使用新的多段日誌文件。

2. 嘗試對數據庫文件得到排他鎖(若是其餘的事務仍持有共享鎖,則嘗試失敗,會返回SQLITE_BUSY給調用者。事務停止)。

3. 把全部的已更改的頁或有選擇的頁寫回到數據庫文件上。寫回頁面就地進行。標誌着緩衝區複製了這些頁(此次就不把數據庫文件刷新到磁盤上了)。若是是由於頁緩衝區滿了,而要寫數據庫文件,頁面管理器不馬上提交事務。事務可能會繼續改變其它的頁。在隨後的更改寫到數據庫文件以前,這三步又會在重複一邊。

頁面管理器爲寫文件而得到的排他鎖直到事務結束才釋放。意味着其餘的應用程序將不會打開其餘的(讀或寫)事務,從頁面管理器第一次寫數據庫文件,直到事務提交或停止爲止。對於短的事務,更新都在緩衝區內進行,排他鎖將在提交時被收回。

1.4.4 提交操做

SQLite遵照一個稍微不一樣的提交協議取決於提交的事務是值修改了一個數據庫仍是修改了多個數據庫。

1.4.4.1 單數據庫狀況

提交一個讀事務是簡單的。頁面管理器從數據庫文件釋放了共享鎖,並清除了頁緩衝區。爲了提交一個寫事務,頁面管理器執行以下步驟:

1. 得到對數據庫文件的排他鎖(若是鎖獲取失敗,向調用者返回SQLITE_BUSY。如今不能提交事務,由於其餘的事務還在讀數據庫。)。把全部的修改過的頁面寫回數據庫文件,按照緩存刷新的算法1到3.

2. 頁面個管理器作一個fsync系統調用,把數據庫文件刷新到磁盤上。

3. 刪除日誌文件。

4. 最終,釋放數據庫文件的排他鎖,並清除頁緩衝區。

事務提交點出如今回滾日誌文件被刪的瞬間。在那以前,若是電源故障或發生崩潰,事務就會被認爲在提交過程當中發生失敗。下次SQLite讀取數據庫時,將會回滾此次事務對數據庫的影響。

1.4.4.2 多數據庫狀況

提交協議會涉及多一點,相似於一個事務在分佈式系統中提交。虛擬機模塊實際驅動提交協議,這做爲協調提交。每一個頁管理器作本身本地那部分的提交。對於只改寫了一個數據庫(不包括臨時數據庫)的一個讀事務或寫事務來講,協議在每一個涉及的數據庫上執行了一次正常的提交。若是事務修改了多個數據庫文件,以下的提交協議被執行:

1. 釋放事務沒更新的數據庫的共享鎖。

2. 獲取事務已更新的數據庫的排他鎖。

3. 新建一個新的主日誌文件。把全部單個的回滾日誌文件名填寫到主日誌文件,並把主日誌和日誌目錄刷新到磁盤上(臨時數據庫名不包含在主日誌中)。

4. 把主日誌文件名字寫入此主日誌文件包括的全部單獨的回滾日誌文件中去,並刷新回滾日誌(直到事務提交時,頁面管理器才知道是多數據庫的一部分。只有在這時才知道是多數據庫事務的一部分。)。

5. 刷新單獨的數據庫文件。

6. 刪除主日誌文件而且刷新日誌目錄。

7. 刪除全部的回滾日誌文件。

8. 釋放數據庫文件的排他鎖,而且清除頁面緩存。

事務被認爲在主日誌文件刪除時已經被提交。在此以前,若是電力故障或系統崩潰,事務會被認爲是在提交處理過程當中失敗。當SQLite下次讀取數據庫時,將會恢復他們各自以前的狀態,並開始事務。

若是主數據庫是臨時文件(或內存數據庫)。SQLite不保證多數據庫事務的原子性。就是說,全局的恢復多是行不通的。由於並無創建一個主日誌。虛擬機逐個的執行簡單的commit命令。所以每一個數據庫文件都被保證是內部是原子的。所以在發生故障時,有的數據庫會改變有的不會。

1.4.5 語句執行

對於語句子事務級別的操做:讀,寫,提交在下邊討論。

1.4.5.1 讀操做

一個語句子事務經過主用戶事務來讀取一頁。全部的規則都遵循主事務

1.4.5.2 寫操做

寫操做分爲兩部分:鎖定和日誌。一個與句子事務經過主用戶事務得到鎖。可是語句日誌不一樣,經過使用單獨的臨時語句日誌文件處理。QSLite把一部分日誌記錄寫在在語句日誌中,一部分寫在主回滾日誌中。當一個子事務試圖經過調用sqlite3pager_write操做使一個頁變得可寫時,頁面管理器器執行以下兩個備選的操做:1. 若是這個頁不在回滾日誌中,會加一個新的記錄到回滾日誌中。2. 不然,若是頁不在語句日誌中,會加一個新的日誌記錄到語句日誌中(當事務寫第一條記錄到文件時,頁面管理器創建日誌文件)。頁面管理器不會刷新語句日誌,由於不要求崩潰恢復。若是電源故障發生,則主回滾日誌會處理數據庫的回滾。一個緩衝區中的頁可能又是回滾日誌中的頁的一部分,又是語句日誌中的頁的一部分:回滾日誌有最老舊的頁鏡像。

1.4.5.3 提交操做

語句提交很簡單,頁面管理器刪除語句日誌文件。

1.4.6 事務停止

在SQLite中恢復事務停止是很簡單的。頁面管理器也許或也許不須要去消除事務對數據庫文件的影響。若是事務只在數據庫文件上持有一個保留鎖或未決鎖,就會保證文件不會被改變,頁面管理器刪除日誌文件,並放棄全部頁面緩衝區的髒頁。不然,一些頁就會被事務寫回數據庫文件,頁面管理器執行以下回滾操做:頁面管理器逐個的讀取回滾日誌記錄,並從新從記錄中加載頁鏡像。(數據庫頁最多隻能被事務記錄一次,日誌記錄在頁鏡像以前存儲。)所以,在日誌記錄掃描結束時,數據庫被從新加載成它開始事務以前的原始狀態。若是事務擴大了數據庫,頁面管理器就會截斷數據庫到原先的規模。頁面管理器就會首先刷新數據庫文件,而後刪除回滾日誌文件。

1.4.7 語句停止

語句操做節指出,一條語句可能又被加載到語句日誌中,又被加載到回滾日誌中。SQLite須要從語句日誌和回滾日誌中回滾全部的日誌記錄。當一個語句子事務在回滾日誌中寫第一條日誌記錄時,頁面管理器會保存記錄在內存中數據結構的位置。從這條記錄開始,直到回滾日誌文件的結束,都被子事務寫。頁面管理器從日誌記錄從新加載頁面鏡像。而後刪除語句日誌文件,可是留着回滾日誌文件而不改變內容。當語句子事務開始時,頁面管理器記錄數據庫的大小。頁面管理器截斷數據庫文件到不一樣大小。

1.4.8 從失敗恢復

在崩潰或系統故障後,不一致的數據可能會留在數據庫文件中。當沒有應用程序正在進行更新數據庫時,可是此時出現了一個回滾日誌文件,意味着以前的事務可能已經失敗,SQLite可能要在能夠被再次正常使用以前先要去除事務的影響,恢復數據庫。若是相應的數據庫文件是無鎖的或是有共享鎖,那麼說回滾日誌文件是熱的。當一個寫事務在完成途中或者是失敗了,那麼日誌是熱的。然而,若是是多數據庫事務,並且也沒有主日誌,那麼說回滾日誌不是熱的,這意味着失敗發生時,事務被提交。一個熱的日誌意味着它須要被回滾以從新加載以保持數據庫的一致性。

若是不涉及主日誌,那麼當存在數據庫文件沒有保留鎖或更強的鎖,而且文件回滾日誌存在時,那麼回滾日誌就是熱的。(有保留鎖的事務創建一個回滾日誌文件,這個文件不是熱的。)若是一個主日誌名出如今回滾日誌中,若是主日誌存在而且在相應數據庫文件上沒有保留鎖,那麼回滾日誌是熱的。當一個數據庫開始時,在大多數的DBMS中,事務管理器當即在數據庫上啓動一個恢復恢復操做。SQLite用一個延遲的恢復。如在讀操做節中,當執行從數據庫中第一次讀一頁(任何頁),頁面管理器經過恢復邏輯而且僅當恢復日誌是熱的時候,恢復數據庫。

若是當前的應用程序只對數據庫文件有讀的權限,在文件或包含目錄上沒有寫的權限,則恢復會失敗,應用程序會從SQLite庫中獲得一段意外的錯誤代碼。

在實際上從文件讀取一頁以前,會執行以下的恢復步驟:

1. 得到對數據庫文件的共享鎖。若是不能得到鎖,會返回一個SQLITE_BUSY給應用程序。

2. 檢查數據庫文件是否有熱日誌,若是數據庫沒有熱日誌,恢復操做結束。若是有熱日誌,日誌會按隨後的回滾算法回滾。

3. 若是得到了對數據庫文件的排他鎖(經過未決鎖)。(頁面管理器不會獲取一個保留鎖,由於這會使得其餘的頁面管理器認爲日誌再也不是熱的而且讀數據庫。須要一個排他鎖由於做爲恢復工做的一部分,將要寫數據庫文件。)若是獲取鎖失敗,意味着其餘的頁面管理器已經在試圖作回滾。在這種狀況下,會丟棄全部鎖,關閉數據庫文件,並返回SQLITE_BUSY給應用程序。

4. 從日誌文件中讀取全部的日誌記錄並撤銷他們。這將從新加載數據庫到它執行崩潰了的事務之前的狀態,所以,數據庫在一個一致的狀態。

5. 刷新數據庫文件。這保護了在電源故障或其餘崩潰的狀況下的數據庫的完整性。

6. 刪除回滾日誌。

7. 若是是安全狀態下的話,刪除主日誌文件。(此步驟是可選的)

8. 釋放排他鎖(和未決鎖),可是保留共享鎖。(這是由於頁面管理器用sqlite3_get函數執行恢復)在前面的算法成功執行以後,數據庫文件保證被從新加載到已失敗事務執行以前的狀態。這時從文件讀是安全的。

若是沒有單個的回滾日誌指向主日誌的話,主日誌是過期的。爲了弄清主日誌是不是陳舊的,頁面管理器首先讀取主日誌來得到全部回滾日誌的名字。而後檢查每一個回滾日誌。若是其中任何一個存在並指回到主日誌,則主日誌是不過期的。若是全部的回滾日誌要麼丟失要麼指向其餘的主日誌要麼根本沒有主日誌的話,則主日誌是過期的,頁面管理器刪除日誌。沒有要求過期的主日誌應該被刪除。這樣作的惟一目的就是釋放被佔用的磁盤空間。

1.4.9 檢查點

爲了減小在失敗恢復時的工做量,大多數的DBMS按期在數據庫上執行檢查點。SQLite同一時間在一個數據庫文件上最多隻能有一個寫事務。日誌文件只包含了來自特定事務的日誌記錄。此時,SQLite不須要執行檢查點,而且也沒有任何的內嵌的檢查點的邏輯。當一個事務提交時,SQLite確保在刪除日誌文件以前,全部來自於事務的更新都是在數據庫文件中的。

相關文章
相關標籤/搜索