溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

SQLite原子提交的原理是什么

發(fā)布時(shí)間:2021-08-03 17:05:51 來源:億速云 閱讀:200 作者:Leah 欄目:數(shù)據(jù)庫

這篇文章給大家介紹SQLite原子提交的原理是什么,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

1.0 簡(jiǎn)介

“原子提交”是SQLite這種支持事務(wù)的數(shù)據(jù)庫的一個(gè)重要特性。原子提交意味著某個(gè)事務(wù)中數(shù)據(jù)庫的變化會(huì)完整完成或者根本不完成。原子提交意味著不同的寫入分別寫入到數(shù)據(jù)庫的不同部分就似同時(shí)發(fā)生在同一個(gè)時(shí)間點(diǎn)一樣。

 實(shí)際上硬件會(huì)連續(xù)的寫到海量存儲(chǔ)器中,只是寫一個(gè)扇區(qū)所用的時(shí)間非常少。所以,同時(shí)或瞬間寫入到數(shù)據(jù)文件的不同部分成為可能。SQLite的原子提交邏輯會(huì)使得一個(gè)事務(wù)中的變化就象同時(shí)發(fā)生的一樣。

事務(wù)的原子是SQLite的重要特性,即使事務(wù)由于操作系統(tǒng)出錯(cuò)或掉電發(fā)生中斷也能保持其原子性。

本文描述了SQLite實(shí)現(xiàn)原子操作的技術(shù)。

2.0 硬件設(shè)定

在這往篇文章中,我們把海量存儲(chǔ)特指定為“硬盤”,即使它可能是flash memory.

我們假定硬盤是以扇區(qū)為單位進(jìn)行整塊寫入的。我們不能單獨(dú)修改硬盤的小于扇區(qū)的部分。如果需要修改硬盤小于扇區(qū)的部分,你也必須整個(gè)讀入此部分所在扇區(qū),對(duì)此扇區(qū)進(jìn)行修改,然后將整個(gè)扇區(qū)寫回硬盤。

在傳統(tǒng)的Spinning disk中,扇區(qū)是最小的傳輸單元---無論是讀還是寫。然而,對(duì)于flash memory,每次讀的最小數(shù)目通常都遠(yuǎn)小于最小寫操作數(shù)目。SQLite 只關(guān)心寫操作的最小數(shù)目,因此在本文中,當(dāng)我們說“扇區(qū)”的時(shí)候,就是指單次寫入的最少字節(jié)總數(shù)。

SQLite 3.3.14以前的版本,我們假定任何情況下,一個(gè)扇區(qū)是512字節(jié)。這是一個(gè)編譯時(shí)設(shè)定的值,而且從沒針對(duì)更大數(shù)進(jìn)行測(cè)試過。當(dāng)磁盤驅(qū)動(dòng)器內(nèi)部使用的是以512字節(jié)為單位的扇區(qū)時(shí),512字節(jié)的假定顯得非常合理。然而,現(xiàn)在的磁盤都已經(jīng)發(fā)展到4k每扇區(qū)了。同樣, flash memory 的扇區(qū)大小通常都大于512字節(jié)。因此,從3.3.14版本開始,SQLite有一個(gè)函數(shù)去獲取文件系統(tǒng)的扇區(qū)真實(shí)大小。在當(dāng)前的實(shí)現(xiàn)中(3.5.0),這個(gè)函數(shù)仍然簡(jiǎn)單的返回512—因?yàn)樵趙in32及unix環(huán)境下,沒有標(biāo)準(zhǔn)方法去取得扇區(qū)的真實(shí)大小。但這個(gè)方法在人們需要針對(duì)他們應(yīng)用進(jìn)行調(diào)整的時(shí)候是非常有意義的。

SQLite并假定扇區(qū)寫操作是原子的。然而,我們假定扇區(qū)寫操作是線性的。所謂“線性”是指,當(dāng)開始扇區(qū)寫操作時(shí),硬件從前一個(gè)扇區(qū)的結(jié)束點(diǎn)開始,然后一字節(jié)一字節(jié)的寫入,直到此扇區(qū)的結(jié)束點(diǎn)。這個(gè)寫操作可能是從尾向頭寫,也可能是從頭向尾寫。如果在一個(gè)扇區(qū)寫入操作時(shí)發(fā)生掉電故障,這個(gè)扇區(qū)可能會(huì)一部分已經(jīng)修改完成,還有一部分還沒來得及進(jìn)行修改。SQLite的關(guān)鍵設(shè)定是這樣的:如果一個(gè)扇區(qū)的任何部分發(fā)生修改,那么不是它開始的部分發(fā)了變化,就是它結(jié)束部分發(fā)生了變化。所以硬件從來都不會(huì)從一個(gè)扇區(qū)的中間部分開始寫入。我們不知道這個(gè)假定是否總是真實(shí)的,但無論如何,看起來還是蠻合理的。

上段中,SQLite并沒有假定扇區(qū)寫操作是原子的。在SQLite3.5.0版本中,新增了一個(gè)VFS(虛擬文件系統(tǒng))接口。SQLite通過VFS與實(shí)際的文件系統(tǒng)進(jìn)行交互。SQLite已經(jīng)為windows及unix編寫了一個(gè)缺省的VFS實(shí)現(xiàn)。并且可以讓用戶在運(yùn)行時(shí)實(shí)現(xiàn)一個(gè)自定義的VFS實(shí)現(xiàn)。VFS接口有一個(gè)方法叫:xDeviceCharacteristics.此方法讀取實(shí)際的文件系統(tǒng)各種特性。xDeviceCharacteristics方法可以指明扇區(qū)寫操作是原子的,如果確實(shí)指定扇區(qū)寫是原子的,SQLite是不會(huì)放過這等好處的。但在windows及unix中,缺省xDeviceCharacteristics的實(shí)現(xiàn)并沒有指明扇區(qū)寫是原子的,所以這些優(yōu)化通常會(huì)忽略掉了。

SQLite假定操作系統(tǒng)會(huì)對(duì)寫進(jìn)行緩沖,因此寫入請(qǐng)求返回時(shí),有可能數(shù)據(jù)還沒有真實(shí)的寫入到存儲(chǔ)中。SQLite 同時(shí)還假定這種寫操作會(huì)被操作系統(tǒng)記錄。因此, SQLite需要在關(guān)鍵點(diǎn)做"flush" 或 "fsync" 函數(shù)調(diào)用。SQLite假定flush或fsync在數(shù)據(jù)沒有真實(shí)的寫入到硬盤之前是不會(huì)返回的。不幸的是,我們知道在一些windows及unix版本中,缺少flush或fsync的真正實(shí)現(xiàn)。這使得SQLite在寫入一個(gè)提交發(fā)生掉電故障后數(shù)據(jù)文件得到損壞。然而,這不要緊,SQLite能夠做一些測(cè)試或補(bǔ)救。SQLite假定操作系統(tǒng)會(huì)是廣告中那樣漂亮運(yùn)行。如果這些都不是問題,那么剩下的只期望你家的電源不要間歇性的休息。

SQLite假定文件增長(zhǎng)方式是指新分配的文件空間,剛分配的時(shí)候是隨機(jī)內(nèi)容,后來才被填入實(shí)際的數(shù)據(jù)。換而言之,文件先變大,然后再填充其內(nèi)容。這是一悲觀假定,因而SQLite不得不做一些額外的操作來防止因斷電發(fā)生的破壞數(shù)據(jù)文件—發(fā)生在文件大小已經(jīng)增大,而文件內(nèi)容還沒完全填入之間的掉電。VFS的xDeviceCharacteristics可以指明文件系統(tǒng)是否總是先寫入數(shù)據(jù)然后才更變文件大小的。(這就是那個(gè):SQLITE_IOCAP_SAFE_APPEND屬性,如果你想查看代碼的話)
當(dāng)xDeviceCharacteristics方法指示了文件內(nèi)容先寫入然后才改變文件大小的話,SQLite會(huì)減少一些相當(dāng)?shù)臄?shù)據(jù)保護(hù)及錯(cuò)誤處理過程,這將大大減少一個(gè)提交磁盤IO操作。然而在當(dāng)前的版本,windows及unix的VFS實(shí)現(xiàn)并沒有這樣假定。

SQLite假定文件刪除從用戶進(jìn)程角度來講是原子的。也就說當(dāng)SQLite要求刪除一個(gè)文件,也在這刪除的過程中間,斷電了,一旦電源恢復(fù),只有下列二種情況之一分發(fā)生:文件仍然存在,所有內(nèi)容都沒有發(fā)生變化;或者文件已經(jīng)被刪除掉了。如果電源恢復(fù)之后,文件只發(fā)生了部分刪除,或者部分內(nèi)容發(fā)生了變化或清除,或者文件只是清空,那么數(shù)據(jù)庫還有用才怪呢。

SQLite假定發(fā)現(xiàn)或修改由于宇宙射線,熱噪聲,量子波動(dòng),設(shè)備驅(qū)動(dòng)bug等等其他可能所引發(fā)的錯(cuò)誤,都由操作系統(tǒng)或硬件來完成。SQLite并不為此類問題增加任何數(shù)據(jù)冗余處理。SQLite假定在寫入之后去讀取所獲得的數(shù)據(jù),是與寫入的數(shù)據(jù)完全一致的!

3.0 單個(gè)文件提交

我們著手觀察SQLite在針對(duì)一個(gè)數(shù)據(jù)庫文件時(shí),為保證一個(gè)原子提交所采取的步驟。關(guān)于在多個(gè)數(shù)據(jù)庫文件之間為防止電源故障損壞數(shù)據(jù)庫及保證提交的原子性所采用的技術(shù)及具體的文件格式在下一節(jié)進(jìn)行討論。

3.1 初始狀態(tài)

當(dāng)一個(gè)數(shù)據(jù)庫第一次打開時(shí)計(jì)算機(jī)的狀態(tài)示意圖如右圖所示。圖中最右邊("Disk”標(biāo)注)表示保存在存儲(chǔ)設(shè)備中的內(nèi)容。每個(gè)方框代表一個(gè)扇區(qū)。藍(lán)色的塊表示這個(gè)扇區(qū)保存了原始資料。圖中中間區(qū)域是操作系統(tǒng)的磁盤緩沖區(qū)。在我們的案例開始的時(shí)候,這些緩存是還沒有被使用—因此這些方框是空白的。圖中左邊區(qū)域顯示SQLite用戶進(jìn)程的內(nèi)存。因?yàn)檫@個(gè)數(shù)據(jù)庫聯(lián)接剛剛打開,所以還沒有任何數(shù)據(jù)記錄被讀入,所以這些內(nèi)存也是空的。

 SQLite原子提交的原理是什么SQLite原子提交的原理是什么

SQLite原子提交的原理是什么

3.4 申請(qǐng)一個(gè)Reserved Lock

在修改一個(gè)數(shù)據(jù)庫之前,SQLite首先得擁有一個(gè)針對(duì)數(shù)據(jù)庫文件的“Reserved”鎖。Reserved鎖類似于共享鎖,它們都允許其他數(shù)據(jù)庫連接讀取信息。單個(gè)Reserved
鎖能夠與其他進(jìn)程的多個(gè)共享鎖一起協(xié)作。然后一個(gè)數(shù)據(jù)庫文件同時(shí)只能存在一個(gè)Reserved 。因此只能有一個(gè)進(jìn)程在某一時(shí)刻嘗試去寫一個(gè)數(shù)據(jù)庫文件。

Reserved 鎖的存在是宣告一個(gè)進(jìn)程將打算去更新數(shù)據(jù)庫文件,但還沒有開始。因?yàn)檫€沒有開始修改,因此其他進(jìn)程可以讀取數(shù)據(jù),但不應(yīng)該去嘗試修改該數(shù)據(jù)庫。

SQLite原子提交的原理是什么

3.5 生成一個(gè)回滾日志文件

在修改數(shù)據(jù)庫文件之前,SQLite會(huì)生成一個(gè)單獨(dú)的回滾日志文件,并在其中寫進(jìn)將被修改的頁的原始數(shù)據(jù)?;貪L日志文件意味它將包含了所有可以將數(shù)據(jù)庫文件恢復(fù)到原始狀態(tài)的數(shù)據(jù)。

回滾日志文件有一個(gè)小的頭部(圖中綠色標(biāo)記部分)記錄了數(shù)據(jù)庫文件的原始大小。因此,如果一旦即使數(shù)據(jù)庫文件變大,我們還是會(huì)知道它原始大小。數(shù)據(jù)庫文件中被修改的頁碼及他們的內(nèi)容都被寫進(jìn)了回滾日志文件中。

當(dāng)一個(gè)新文件剛被創(chuàng)建,大部分的桌面操作系統(tǒng)(windows,linux,macOSX)實(shí)際并不會(huì)馬上寫入數(shù)據(jù)到硬盤。此文件還只是存在于操作系統(tǒng)磁盤緩存中。這個(gè)文件還不會(huì)立即寫到存儲(chǔ)設(shè)備中,一般都會(huì)有一些延遲,或者到操作系統(tǒng)相當(dāng)空閑的時(shí)候。用戶的對(duì)于文件生成感覺是要遠(yuǎn)遠(yuǎn)快(先)于其真實(shí)的發(fā)生磁盤I/O操作。右圖中我們用圖例說明了這一點(diǎn),當(dāng)新的回滾日志文件創(chuàng)建之后,它還只是出現(xiàn)在操作系統(tǒng)磁盤緩存之中,還沒真實(shí)在寫入到硬盤之上。

SQLite原子提交的原理是什么

SQLite原子提交的原理是什么

SQLite原子提交的原理是什么

3.8 獲得一個(gè)獨(dú)享(Exclusive)鎖

在修改數(shù)據(jù)庫文件本身之前,我們必須取得一個(gè)針對(duì)此數(shù)據(jù)庫文件的獨(dú)享鎖。取得此鎖的過程是分二步走的。首先SQLite取得一個(gè)“臨界”(Reserved)鎖,然后將此鎖提升成一個(gè)獨(dú)享鎖。

一個(gè)臨界鎖允許其他所有已經(jīng)取得共享鎖的進(jìn)程從數(shù)據(jù)庫文件中繼續(xù)讀取數(shù)據(jù)。但是它會(huì)阻止新的共享鎖的生成。也就說,臨界鎖將會(huì)防止因大量連續(xù)的讀操作而無法獲得寫入的機(jī)會(huì)。這些讀取者可能有一打,也可能上百,甚至于上千。任何一個(gè)讀取者在開始讀取之前都要申請(qǐng)一個(gè)共享鎖,然后開始讀取它需要的數(shù)據(jù),然后釋放共享鎖。然而存在這樣一種可能:如果有太多的進(jìn)程來讀取同一個(gè)數(shù)據(jù)文件,在老的進(jìn)程釋放它的共享鎖之前總是會(huì)有新的進(jìn)程申請(qǐng)共享鎖,因此不會(huì)存在某一時(shí)刻這個(gè)數(shù)據(jù)庫文件上沒有共享鎖的存在,也因此寫入者不會(huì)擁有取得一個(gè)獨(dú)享鎖的機(jī)會(huì)。臨界鎖的概念可以使現(xiàn)有的讀取者完成他們的讀取,同時(shí)阻止新的讀取者讀取,最后所有的讀取者都讀完之后,這個(gè)臨界鎖就可以被提升為獨(dú)享鎖了。

SQLite原子提交的原理是什么

SQLite原子提交的原理是什么

3.10 刷新變更到存儲(chǔ)

一個(gè)附加的flush操作是必要的,這樣才可以保證針對(duì)此文件的變化真正的寫入到永久存儲(chǔ)器中。這也是一個(gè)重要的步驟,將可以保證數(shù)據(jù)在掉電之后也將是完整無損的。然而,因?yàn)閷懭氲酱疟P所固有的慢,這個(gè)步驟同上面3.7節(jié)將日志文件flush到磁盤中一樣,占據(jù)了SQLIite事務(wù)提交操作的絕大部分時(shí)間。

SQLite原子提交的原理是什么

3.11 刪除回滾日志文件

當(dāng)數(shù)據(jù)變更已經(jīng)安全的寫入到硬盤之后,回滾日志文件就沒有必要再存在了,因此立即刪除之。如果在刪除之前又掉電了或者系統(tǒng)崩潰了,恢復(fù)進(jìn)程(在后面將會(huì)提到)會(huì)將日志文件的內(nèi)容寫回到數(shù)據(jù)庫文件中—即使這個(gè)數(shù)據(jù)庫沒有發(fā)生變化。如果刪除之后系統(tǒng)崩潰或者又停電了,看起來好象所有變化都已經(jīng)寫入到磁盤。因此,SQLite判斷數(shù)據(jù)庫文件是否完成了變更是依賴于回滾日志文件是否存在。

刪除一個(gè)文件實(shí)際上不是一個(gè)原子操作,但從用戶進(jìn)程的角度來看,它是一個(gè)原子操作。一個(gè)進(jìn)程總是可以向操作系統(tǒng)詢問某個(gè)文件存在否,而它得到的答案只有“YES”和“NO”二種。在一個(gè)事務(wù)提交的中間,系統(tǒng)崩潰或又停了,之后,SQLite會(huì)向操作系統(tǒng)咨詢回滾日志文件存在與否,如果存在,則這個(gè)事務(wù)是沒有完成,被中斷了,需要對(duì)數(shù)據(jù)庫文件進(jìn)行回滾。如果日志文件不存在,意味著事務(wù)已經(jīng)提交ok了。.

事務(wù)存在的可能性依賴于是否有回滾日志文件。刪除一個(gè)文件對(duì)于一個(gè)用戶進(jìn)程來說是原子性的。因此,整個(gè)事務(wù)看起來也是一個(gè)原子操作。.

SQLite原子提交的原理是什么

SQLite原子提交的原理是什么

SQLite原子提交的原理是什么

SQLite原子提交的原理是什么

SQLite原子提交的原理是什么 

4.4 回滾沒有完成的變更

一旦進(jìn)程獲得一個(gè)獨(dú)享鎖,它就被允許更新數(shù)據(jù)庫文件。然后從日志文件中讀取原始的內(nèi)容,并寫回到數(shù)據(jù)庫文件中。是否還記得在這個(gè)被中止的事務(wù)的開始的時(shí)候,數(shù)據(jù)庫文件原始大小已經(jīng)被寫進(jìn)了日志文件的頭部。SQLite使用這些信息來截?cái)鄶?shù)據(jù)庫文件,讓文件恢復(fù)到原始大小—如果這個(gè)沒有完成的事務(wù)使得數(shù)據(jù)庫變大了。最后,數(shù)據(jù)庫文件大小及內(nèi)容肯定與這個(gè)被中斷事務(wù)開始之前是一樣的了。

SQLite原子提交的原理是什么

SQLite原子提交的原理是什么SQLite原子提交的原理是什么SQLite原子提交的原理是什么SQLite原子提交的原理是什么SQLite原子提交的原理是什么SQLite原子提交的原理是什么

SQLite原子提交的原理是什么SQLite原子提交的原理是什么

6.0原子操作的一些實(shí)現(xiàn)細(xì)節(jié)

3.0節(jié)大致描述了SQLite中原子提交是如何工作的。但它略過了許多重要的細(xì)節(jié)。下面的這些部分將嘗試補(bǔ)充說明這些地方。

6.1 總是記錄整個(gè)扇區(qū)

當(dāng)數(shù)據(jù)庫文件的原始代碼被寫入到日志文件時(shí)(參見3.5節(jié)),SQLite總是寫入完整的扇區(qū),即使數(shù)據(jù)文件頁大小是小于一個(gè)扇區(qū)。由于歷史上的原因,SQLite的扇區(qū)大小原先是固定為512字節(jié),此外由于最小的頁大小是512字節(jié),因此這從來都不是一個(gè)問題。自SQLite3.3.14版本以來,SQLite便有可能使用最小扇區(qū)大于512字節(jié)的海量存儲(chǔ)設(shè)備。所以,自從3.3.14版本開始,只要一個(gè)扇區(qū)中的任何一頁被寫進(jìn)到回滾日志文件中,那么同一扇區(qū)中的所有節(jié)都會(huì)寫入到日志文件中去。

將扇區(qū)中的所有頁都寫入日志文件中去是很重要的,它將可以防止因?yàn)樵趯懸粋€(gè)扇區(qū)時(shí)發(fā)生掉電故障而導(dǎo)致數(shù)據(jù)庫損壞。假充頁1,2,3,4都是保存扇區(qū)1中,頁2被修改了。為了將這種變更寫回到頁2中,實(shí)際的硬件設(shè)備將也會(huì)同時(shí)重寫頁1,3及4的內(nèi)容—這是因?yàn)橛布仨氁陨葏^(qū)為單元作寫操作。如果一個(gè)寫操作正在進(jìn)行的時(shí)候,由于電源的原因,發(fā)生了中斷,這樣,頁1,3,4中會(huì)有1頁或者多頁數(shù)據(jù)是不完整,不正確的。因此為了防止這種損壞,數(shù)據(jù)庫文件的同一扇區(qū)中的所有頁都必須寫入到日志文件中去。

6.2 寫日志文件時(shí)垃圾的處理

當(dāng)向一個(gè)日志文件追加數(shù)據(jù)時(shí),SQLite總是悲觀的假定文件會(huì)首先變大,變大的部分會(huì)填之一些無效的垃圾數(shù)據(jù),在此之后正確的數(shù)據(jù)才會(huì)取代這些垃圾。換而言之,SQLite假定文件先改變大小,然后內(nèi)容才會(huì)寫進(jìn)來。如果在文件大小增大之后,在內(nèi)容還沒有寫完之前發(fā)生掉電故障,那么這些日志文件就會(huì)留下一些垃圾數(shù)據(jù)在其中。下次當(dāng)電源恢復(fù),另一個(gè)SQLite進(jìn)程就會(huì)看到這些保存了垃圾數(shù)據(jù)的日志文件,并同時(shí)會(huì)把這些垃圾數(shù)據(jù)回滾到數(shù)據(jù)庫文件中去,然后整個(gè)數(shù)據(jù)庫就玩完了。

SQLite采用了兩種預(yù)防措施。第一種,SQLite會(huì)在日志文件的頭部記錄下該日志文件中包含的頁的數(shù)量。這個(gè)數(shù)量初始值是0。所以在嘗試回滾一個(gè)不完整(或不正確)的回滾日志文件時(shí),處理回滾的進(jìn)程會(huì)看到該日志只包含0個(gè)頁面,那么它就會(huì)不對(duì)數(shù)據(jù)庫作任何改變。提交之前,日志文件會(huì)被flush到硬盤中以確保所有的內(nèi)容都同步到硬盤,同時(shí)沒有任何垃圾內(nèi)容留在其中,然后日志文件頭部的頁總數(shù)值才會(huì)置成真實(shí)有效的數(shù)據(jù)(原先數(shù)值是0)。日志文件的頭部總是存放在區(qū)別于所有的頁數(shù)據(jù)之外的獨(dú)立扇區(qū)中,以此來保證它可被單獨(dú)修改并且flush,即使發(fā)生掉電也不會(huì)危及數(shù)據(jù)頁。請(qǐng)注意,日志文件會(huì)被flush兩次:第一次寫頁數(shù)據(jù),第二次是將頁面數(shù)量寫入到文件頭部中。

前面的章節(jié)描述了當(dāng)synchronouspragma設(shè)置成”full”發(fā)生的事情。

PRAGMAsynchronous=FULL;

缺省的synchronous設(shè)置是“full”,所以上面描述是通常會(huì)發(fā)生的情形。然而,如果synchronous設(shè)置成“normal”,那SQLite只會(huì)flush日志文件一次,就是在頁面數(shù)量寫入之后。這將意味著會(huì)有數(shù)據(jù)損壞的風(fēng)險(xiǎn)。因?yàn)橛锌赡鼙恍薷牡捻撁鏀?shù)量(非0)比所有的頁數(shù)據(jù)更早一步寫入到硬盤中。也數(shù)據(jù)的寫入請(qǐng)求雖然會(huì)先被發(fā)起,但SQLite假定底層的文件系統(tǒng)可能會(huì)對(duì)寫入請(qǐng)求重新排序,所以有可能頁面數(shù)量會(huì)先寫到磁盤中,即使是它的寫請(qǐng)求是在最后。所以作為第二個(gè)預(yù)防手段,SQLite會(huì)為日志文件中的每一頁數(shù)據(jù)使用一個(gè)32位的校驗(yàn)和,當(dāng)回滾數(shù)據(jù)時(shí)(節(jié)4.4),這些值用來驗(yàn)證這些頁是否有效。一旦發(fā)現(xiàn)有不正確的校驗(yàn)和時(shí),那么就會(huì)放棄回滾。要注意的是,校驗(yàn)值并不確保頁面數(shù)據(jù)百分百的正確,有極小的可能會(huì)出現(xiàn)即便數(shù)據(jù)錯(cuò)誤校驗(yàn)和也是正確的。但使用校驗(yàn)和還是能使出錯(cuò)的可能性降到少之又少。

注意,如果synchronous設(shè)置成full時(shí)校驗(yàn)和不是必須的。只有當(dāng)synchronous設(shè)置成normal時(shí),我們才使用這些校驗(yàn)和。不過,這些校驗(yàn)和是沒有壞處的,所以無論synchronous設(shè)是什么,它們都包括在日志文件里了。

6.3 提交前緩存溢出

節(jié)3.0描述的提交過程都假設(shè)所有的數(shù)據(jù)庫變更在提交前都適合用戶的內(nèi)存大小。這是通常情況。但有時(shí)一個(gè)非常大的修改在事務(wù)提交前會(huì)超出用戶空間的內(nèi)存緩存大小。在這種情況下,事務(wù)完成之前,緩存必須先將數(shù)據(jù)先寫入到數(shù)據(jù)庫中。

在緩存溢出開始時(shí),這個(gè)數(shù)據(jù)庫聯(lián)接的狀態(tài)如3.6節(jié)提到的。原始的頁數(shù)據(jù)已經(jīng)被寫入到回滾日志文件中了,修改的部分還保存在用戶內(nèi)存中。要處理這種緩存溢出,SQLite會(huì)執(zhí)行3.7節(jié)到3.9節(jié)的內(nèi)容。換言之,回滾日志被flush到硬盤,獨(dú)享鎖已經(jīng)申請(qǐng)到,修改已經(jīng)被寫入到數(shù)據(jù)庫了。但剩余的步驟會(huì)推遲到這個(gè)事務(wù)被真正提交。新的日志文件頭會(huì)追加到回滾日志文件尾部(處于它自己?jiǎn)为?dú)的扇區(qū)中),獨(dú)享鎖仍然保留,但其他處理則回到3.6節(jié).當(dāng)這個(gè)事務(wù)提交時(shí),或者另外的緩存溢出發(fā)生, 3.7節(jié)及3.9節(jié)會(huì)再次發(fā)生(3.8節(jié)在第二次或以后過程中被省略掉,因?yàn)楠?dú)享鎖已經(jīng)拿到了)。

一次緩存溢會(huì)使數(shù)據(jù)庫的臨界鎖提升為獨(dú)享鎖。這將減少并發(fā)。一次緩存溢出也會(huì)導(dǎo)致額外的硬盤flush(fsync)操作,這些操作比較慢,因此緩存溢出會(huì)嚴(yán)重降低性能。因此,應(yīng)該盡可能的避免緩存溢出。

7.0 優(yōu)化

性能分析顯示,在大部分的操作系統(tǒng)和環(huán)境下面,SQLite主要耗時(shí)是在磁盤IO上面。如果我們能夠減少磁盤IO數(shù)量就會(huì)顯著的提高SQLite的性能。本節(jié)將描述SQLite在不影響提交原子性的前提下,為減少磁盤IO數(shù)量所采用的一些技術(shù)。

7.1 在事務(wù)間保存緩存

事務(wù)提交處理過程中,節(jié)3.12指出一旦共享鎖被釋放,用戶空間所有的緩存的數(shù)據(jù)庫內(nèi)容鏡像都必須得拋棄。這是因?yàn)槿绻麤]有一個(gè)共享鎖,其他進(jìn)程就可以隨便修改數(shù)據(jù)庫的內(nèi)容,所以任何一塊數(shù)據(jù)庫數(shù)據(jù)在用戶空間的緩存都可能會(huì)過期無效。因此,每一個(gè)新的事務(wù)會(huì)嘗試去重新讀取它以前讀取過的數(shù)據(jù)。這并不像聽起來這樣糟糕,因?yàn)榈谝淮巫x取過的數(shù)據(jù)還可能存在于操作系統(tǒng)的磁盤緩存中。所以這個(gè)讀實(shí)際上只是一次數(shù)據(jù)從內(nèi)核空間到用戶空間的復(fù)制。但盡管這樣,這還是需要占用cpu時(shí)間的。

自從SQLite3.3.14開始,新增了一個(gè)機(jī)制用來減少一些不必要的數(shù)據(jù)重復(fù)讀取操作。最新的SQLite中,用戶空間的頁面緩存在用戶鎖釋放之后仍然保留。之后,當(dāng)要開始一個(gè)新事務(wù),在取得一個(gè)共享鎖之后,SQLite會(huì)嘗試檢查在此期間是否有進(jìn)程對(duì)數(shù)據(jù)進(jìn)行了修改。如果在鎖釋放這段時(shí)間,數(shù)據(jù)庫發(fā)生過任何的變化,那么用戶空間的緩存就會(huì)被釋放。但通常情況下,數(shù)據(jù)文件是沒有被修改過的,因此用戶空間的緩存因而得到保留,一些不必要的讀取操作從而得到了減免。

為了判斷數(shù)據(jù)庫文件是否被修改過,SQLite使用了一個(gè)計(jì)數(shù)器,存于數(shù)據(jù)庫文件頭部(處于字節(jié)24~27),每針對(duì)數(shù)據(jù)庫做一次修改,就會(huì)對(duì)此值進(jìn)行一回增長(zhǎng)。SQLite會(huì)在釋放一個(gè)鎖之前記錄一份這個(gè)值的。當(dāng)下回取得鎖之后,就會(huì)去與原先保存的值進(jìn)行比較。如果值不一致,則必須清除這些緩存,反之緩存可以重新使用。

7.2 獨(dú)享訪問模式

SQLite從3.3.14版本之后增加一個(gè)“獨(dú)享訪問模式”概念。當(dāng)處于獨(dú)享訪問模式時(shí),SQLite會(huì)在一個(gè)事務(wù)完成之后仍然保留獨(dú)享鎖。這將阻止其他進(jìn)程訪問這個(gè)數(shù)據(jù)庫;由于大部分的開發(fā)都只有一個(gè)進(jìn)程訪問數(shù)據(jù)庫,所以大部分情況下這不是一個(gè)嚴(yán)重的問題。獨(dú)享訪問模式的好處可以在三個(gè)方面減少磁盤IO數(shù)量:

1)       不再需要在每個(gè)事務(wù)完成之后修改文件頭部的變更計(jì)數(shù)器。這可以為回滾日志及數(shù)據(jù)庫文件減少一次頁寫入。

2)       沒有其他進(jìn)程會(huì)修改數(shù)據(jù)庫,所以不必在一個(gè)事務(wù)開始的時(shí)候去檢查變更計(jì)數(shù)器或者清除掉用戶空間的緩存。

3)       當(dāng)一個(gè)事務(wù)完成之后,可以采用將日志文件頭清零的方式,而不必去刪除這個(gè)日志文件。這樣就避免了修改日志文件的目錄項(xiàng),也不必釋放日志文件對(duì)應(yīng)的磁盤扇區(qū)。而且,下一個(gè)事務(wù)可以重寫(overwrite)已有日志文件的內(nèi)容,而不是在新的文件后追加新內(nèi)容。在大多數(shù)的操作系統(tǒng)中,重寫操作要遠(yuǎn)快于追加操作。

上述的第三點(diǎn)優(yōu)化,將日志文件頭清空而不是刪除日志文件,不再依賴于一直持有一個(gè)獨(dú)享鎖。在理論上,我們可以在任何時(shí)刻做這項(xiàng)優(yōu)化,并不是只有在獨(dú)享訪問模式時(shí)。This optimization can be set independently of exclusive lock modeusing the journal_mode
pragma asdescribed in section 7.6 below.

7.3 不必將空閑頁寫進(jìn)日志

SQLite數(shù)據(jù)庫的信息被刪除之后,這些被刪除的數(shù)據(jù)所使用的頁會(huì)被加入到空頁鏈表之中。后來的插入操作會(huì)盡量先使用空頁鏈表中的頁。

一些空白頁包含緊要數(shù)據(jù):特別是其他空百頁的位置。但是大多數(shù)的空白頁并不包含有用信息。這類頁被稱之為“葉子”頁。我們可以隨意修改這些葉子頁的內(nèi)容而不會(huì)影響數(shù)據(jù)庫。

因?yàn)槿~子頁的內(nèi)容是不重要的,SQLite避免保存這些葉子頁的內(nèi)容到回滾日志文件中去(3.5節(jié))。如果一個(gè)葉子頁的內(nèi)容被修改了,那么在事務(wù)恢復(fù)過程中這些針對(duì)葉子頁的修改并不會(huì)回滾。這不會(huì)對(duì)數(shù)據(jù)庫產(chǎn)生傷害。同樣的,新的空頁鏈表的內(nèi)容也從不會(huì)在節(jié)3.9中寫回到到數(shù)據(jù)庫,也不會(huì)在節(jié)3.3從數(shù)據(jù)庫讀入。當(dāng)針對(duì)數(shù)據(jù)庫文件的變化包含有空白頁時(shí),這種優(yōu)化可以大量的減少磁盤io操作總數(shù)

7.4 單頁更新及扇區(qū)原子寫

從3.5.0開始,新的VFS接口包含了一個(gè)新的方法:xDeviceCharacteristics ,它能夠讀取實(shí)際的文件系統(tǒng)可能有的特性。xDeviceCharacteristics會(huì)報(bào)告是否文件系統(tǒng)能夠支持扇區(qū)寫原子操作。

回想前面,在一般情況下SQLite假定扇區(qū)寫是線性的,但是非原子的。線性寫從另一個(gè)扇區(qū)結(jié)束點(diǎn)開始一字節(jié)一字節(jié)進(jìn)行修改,直到扇區(qū)的結(jié)束點(diǎn)。如果在寫一個(gè)扇區(qū)時(shí),線性寫會(huì)將修改一個(gè)扇區(qū)的一部分,而另一部分是沒有變動(dòng)的。在一個(gè)扇區(qū)原子寫的情況下,要么整個(gè)扇區(qū)被重寫了,要么扇區(qū)沒有發(fā)生變化。

我們相信大部分現(xiàn)代磁盤驅(qū)動(dòng)器實(shí)現(xiàn)了原子寫操作。當(dāng)停電發(fā)生時(shí),磁盤驅(qū)動(dòng)器可以利用電容中的電能,同時(shí)(或者)利用盤片旋轉(zhuǎn)的角動(dòng)量來完成正在進(jìn)行中的任何操作。然而,在系統(tǒng)寫調(diào)用與磁盤電子器材之間,存在有太多的層次。因此在unix及win32上面的VFS實(shí)現(xiàn)比較安全的選擇是,我們假定扇區(qū)寫操作是非原子性的。On the otherhand, device manufactures with more control over their filesystems might wantto
consider enabling the atomic write property of xDeviceCharacteristics iftheir hardware really does do atomic writes.

當(dāng)一個(gè)扇區(qū)寫是原子性的,并且扇區(qū)大小與頁大小是相同,并且一次數(shù)據(jù)庫的變化只是某一個(gè)單獨(dú)的頁發(fā)生變化時(shí),SQLite會(huì)跳過整個(gè)日志記錄過程,直接簡(jiǎn)單地將被修改過的數(shù)據(jù)寫回到數(shù)據(jù)庫文件。數(shù)據(jù)庫首頁中的變更計(jì)數(shù)器將會(huì)被獨(dú)立進(jìn)行修改—因?yàn)椴粫?huì)對(duì)數(shù)據(jù)庫產(chǎn)生任何影響—即使在計(jì)數(shù)器更新以前發(fā)生停電。.

7.5 FilesystemsWith Safe Append Semantics

SQLite3.5.0中介紹的另一個(gè)優(yōu)化是利用實(shí)際磁盤的“安全追加”行為?;叵肷厦?,SQLite假定為一個(gè)文件追加數(shù)據(jù)時(shí)(特別是針對(duì)回滾日志文件),會(huì)先增大文件的大小,之后才會(huì)把數(shù)據(jù)內(nèi)容寫入。所以在文件的大小已經(jīng)變化,而內(nèi)容還沒有寫完的情況下發(fā)生掉電,那么文件新增部分將會(huì)有一些無效的垃圾數(shù)據(jù)。VFS的xDeviceCharacteristics可以用來指示文件系統(tǒng)是否實(shí)現(xiàn)了“安全追加”語義。這意味著在文件大小變大之前會(huì)先寫入文件內(nèi)容。這就防止當(dāng)系統(tǒng)崩潰或掉電后,垃圾數(shù)據(jù)出現(xiàn)在回滾日志文件中

當(dāng)文件系統(tǒng)有安全追加特性時(shí),SQLite總是保存一個(gè)特別的值:-1來標(biāo)明日志文件中頁總數(shù)。頁面數(shù)量為-1告訴任何嘗試進(jìn)行回滾操作程序頁面數(shù)量需要從日志文件大小計(jì)算得來。同時(shí),這-1值會(huì)從不進(jìn)行修改。所以,在一個(gè)提交過程中,我們節(jié)省一個(gè)flsuh操作及日志文件首頁的扇區(qū)寫入操作。此外,當(dāng)發(fā)生緩存溢出時(shí),也不必要在日志文件后面增加一個(gè)新的日志頭。我們能夠簡(jiǎn)單的在一個(gè)現(xiàn)有的日志文件中添加一些新的頁。

7.6持續(xù)的回滾日志

在許多系統(tǒng)中刪除文件都是一個(gè)昂貴的操作。因此作為一個(gè)優(yōu)化方案,SQLite可以通過配置避免3.11節(jié)中涉及到的刪除操作。在事務(wù)提交時(shí),通過將日志文件的文件頭長(zhǎng)度截為0或是用0重寫文件頭內(nèi)容的方法來代替刪除日志文件。將長(zhǎng)度截為0的做法節(jié)省了必須要對(duì)文件的所在目錄做的修改(因?yàn)槲募琅f存在于這個(gè)目錄中)。重寫文件頭的方案還有另外一個(gè)好處,不必更新文件(許多系統(tǒng)中的i節(jié)點(diǎn))的長(zhǎng)度,而且不需要處理新釋放的磁盤扇區(qū)。更進(jìn)一步講,下一個(gè)事務(wù)的日志文件是通過重寫已有內(nèi)容而產(chǎn)生,而不是在文件末尾追加新內(nèi)容,并且重寫操作通常是要比追加操作更快的。

SQLite可以通過將日志模式設(shè)置為“PERSIST”使提交事務(wù)時(shí)使用用0重寫日志文件頭的方式來代替刪除日志文件。例如:

PRAGMA journal_mode=PERSIST;

在很多系統(tǒng)中,使用持續(xù)的日志模式會(huì)帶來顯著的性能提升。當(dāng)然,缺點(diǎn)就是事務(wù)提交很久以后,日志文件還會(huì)留在磁盤上,占用磁盤空間,導(dǎo)致目錄雜亂。刪除持續(xù)日志文件唯一安全的方法就是提交事務(wù)時(shí)將日志模式設(shè)置為DELETE:

PRAGMA journal_mode=DELETE;

BEGIN EXCLUSIVE;

COMMIT;

注意:因?yàn)槿罩疚募赡芤廊辉谟茫╤ot),如果使用其它途徑刪除持續(xù)日志文件會(huì)導(dǎo)致對(duì)應(yīng)的數(shù)據(jù)庫文件損壞。

從SQLite 3.6.4開始支持 TRUNCATE 日志模式:

PRAGMA journal_mode=TRUNCATE;

截?cái)啵╰runcate)日志模式中,事務(wù)提交時(shí)將日志文件長(zhǎng)度置為0,而不是DELETE模式中的刪除文件或是PERSIST模式中的清零文件頭。 TRUNCATE模式也有PERSIST模式中不需要更新日志文件和數(shù)據(jù)庫所在目錄的好處。因此,截?cái)嘁粋€(gè)文件通常比刪除它要快。TRUNCATE還有一個(gè)好處就是它后面不跟系統(tǒng)調(diào)用(比如:fsync())來將更新同步回磁盤,當(dāng)然如果做了會(huì)更安全。但是在很多現(xiàn)代的文件系統(tǒng)中,截?cái)嗖僮魇窃拥耐讲僮?,并且我們認(rèn)為在遇到斷電情況時(shí),截?cái)嗖僮饕彩前踩?。如果你不確定截?cái)嗖僮髟谀愕奈募到y(tǒng)上的同步性和原子性,并且斷電或宕機(jī)時(shí)的數(shù)據(jù)庫安全對(duì)你很重要,那你應(yīng)該考慮使用其他的日志模式。

在具有同步文件系統(tǒng)的嵌入式操作系統(tǒng)中,TRUNCATE會(huì)導(dǎo)致比PERSIST較慢的行為。提交操作的速度是相同的,但是TRUNCATE操作之后的事務(wù)會(huì)慢一些,因?yàn)橹貙懸汛嬖诘膬?nèi)容比在文件尾追加新內(nèi)容要快。TRUCATE之后新的日志文件總是使用追加操作,而PERSIST則是使用重寫操作。

8.0 原子提交行為測(cè)試

SQLite的開發(fā)者對(duì)SQLite在面對(duì)電源故障及系統(tǒng)崩潰時(shí)所擁有健壯性具有足夠的自信。因?yàn)樽詣?dòng)化的測(cè)試過程做了大量的面對(duì)模擬的電源故障的SQLite恢復(fù)能力測(cè)試。我們稱之為“崩潰測(cè)試”。

SQLite的崩潰測(cè)試是使用一個(gè)修改過的VFS,它能夠模擬種種發(fā)生掉電或系統(tǒng)崩潰時(shí)文件系統(tǒng)發(fā)生的損壞。崩潰測(cè)試用的VFS能夠模擬未完成的扇區(qū)寫操作,未完成的寫操作造成的頁面垃圾,還有無序?qū)懖僮?,一個(gè)測(cè)試場(chǎng)景中各種種各樣的變化。崩潰測(cè)試不停地執(zhí)行事務(wù),讓模擬的掉電或系統(tǒng)崩潰發(fā)生在不同的各種時(shí)刻,造成不同的數(shù)據(jù)損壞。在模擬的事件之后,任何一次測(cè)試重新打開數(shù)據(jù)庫之后,會(huì)檢測(cè)事務(wù)是否完成或者沒有完成,數(shù)據(jù)庫狀態(tài)是否正常。

SQLite的這些崩潰測(cè)試發(fā)現(xiàn)恢復(fù)機(jī)制的大量細(xì)微的BUG(現(xiàn)在都已經(jīng)修復(fù)了)。其中一些BUG是非常模糊的,如果只是單單觀察、分析代碼所不能發(fā)現(xiàn)的。通通過這試驗(yàn),SQLite的開發(fā)者感覺很自信,因?yàn)槠渌臄?shù)據(jù)庫沒有采用類似的崩潰測(cè)試,很可能他們都包含一些沒有被檢測(cè)出的bug,在一次掉電或者系統(tǒng)崩潰之后會(huì)導(dǎo)致數(shù)據(jù)庫損壞。

9.0 會(huì)導(dǎo)致完蛋的事情

SQLite的原子提交機(jī)制已經(jīng)被證明是健壯的。但它也可能被一些不完整的操作系統(tǒng)實(shí)現(xiàn)所陷害。本節(jié)描述一些會(huì)在掉電或系統(tǒng)崩潰下會(huì)導(dǎo)致SQLite數(shù)據(jù)損壞的情形

9.1 缺乏文件鎖實(shí)現(xiàn)

SQLite通過文件系統(tǒng)的鎖來實(shí)現(xiàn)在同一時(shí)刻只有一個(gè)進(jìn)程及一個(gè)數(shù)據(jù)庫聯(lián)接能夠修改數(shù)據(jù)庫。文件鎖機(jī)制由VFS層實(shí)現(xiàn),不同的操作系統(tǒng)具有不同的實(shí)現(xiàn)方式。SQLite依賴于這種實(shí)現(xiàn)的正確性。如果在某種情況下,二個(gè)或更多進(jìn)程能夠在同一時(shí)間寫同一個(gè)數(shù)據(jù)庫文件,這將會(huì)沒有什么好果子吃的。

我們已經(jīng)接收到報(bào)告說windows的網(wǎng)絡(luò)文件系統(tǒng)及NFS的鎖存在一些微妙的缺陷。我們不能驗(yàn)證這些報(bào)告。但是因?yàn)榫W(wǎng)絡(luò)文件系統(tǒng)本身實(shí)現(xiàn)鎖很困難,所以我們沒有理由懷疑這些報(bào)告。首先,既然性能不足,建議你不要在網(wǎng)絡(luò)文件系統(tǒng)中使用SQLite。但是如果你不得不使用一個(gè)網(wǎng)絡(luò)文件來保存SQLite的數(shù)據(jù)文件,那們考慮采用其他的鎖機(jī)制來防止本身的文件鎖機(jī)制出錯(cuò)時(shí)發(fā)生多個(gè)進(jìn)程同時(shí)寫一個(gè)數(shù)據(jù)文件的現(xiàn)象。

蘋果MacOSX預(yù)裝的SQLite版本已經(jīng)擴(kuò)展擁有一種可供選擇的鎖策略可以工作在蘋果支持的所有網(wǎng)絡(luò)文件系統(tǒng)上。這些蘋果使用的擴(kuò)展在多個(gè)進(jìn)程在同時(shí)訪問數(shù)據(jù)庫文件時(shí)工作得很好。不幸的是,這些鎖機(jī)制并不互相排斥,如果一個(gè)進(jìn)程使用AFP鎖去訪問文件,而另一個(gè)進(jìn)程(或許是另一臺(tái)機(jī)器)使用dot-file鎖去訪問這個(gè)文件,那么這二個(gè)進(jìn)程可能發(fā)生沖突,因?yàn)锳FP鎖并不排斥dot-file鎖,反之亦然。

9.2 不完整的磁盤刷新

SQLite 在unix使fsysnc,在win32下面使用FlushFileBuffers,用來將文件內(nèi)容同步到磁盤中(節(jié)3.7及節(jié)3.10)。不幸的是,我們也收到報(bào)告,在許多平臺(tái)上,這二者都沒有象廣告中宣稱的那樣工作。我們聽說FlushFileBuffersc在一些windows版本中,可以通過修改注冊(cè)表,能夠完全禁止其工作。我們也被告之,Linux的一些早先版本,他們的一些文件系統(tǒng)中的fsync完全是一個(gè)空操作。即使是FlushFileBuffers及fsync被告之可以工作的系統(tǒng)中,IDE硬盤經(jīng)常會(huì)撒謊說數(shù)據(jù)已經(jīng)寫入到盤片中,其實(shí)還只是存在狀態(tài)可變的磁盤控制器緩存中。

在Mac你可設(shè)置下面項(xiàng):

PRAGMA fullfsync=ON;

在Mac上設(shè)置fullfsync能夠保證數(shù)據(jù)通過flush會(huì)真實(shí)的寫入到盤片中。但fullfsync會(huì)導(dǎo)致磁盤控制進(jìn)行重設(shè)。這并不是一般意義上的慢,它還會(huì)導(dǎo)致其他磁盤IO降速,所以此項(xiàng)配置并不推薦。

9.3 文件部分地刪除

SQLite假設(shè)從用戶進(jìn)程角度來看是一個(gè)原子操作。當(dāng)刪除過程中發(fā)生掉電,當(dāng)電源恢復(fù)之后,SQLite希望看到文件要么完整的存在,要么根本找不到了。如果操作系統(tǒng)不能做到這一點(diǎn),那事務(wù)就可能不是原子性的了。

9.4 寫入到文件中的垃圾

SQLite的數(shù)據(jù)文件是一種普通的磁盤文件,可以由普通用戶進(jìn)行讀寫。一些流氓進(jìn)程可能會(huì)打開一個(gè)SQLite文件,并在其中寫入一些混亂的數(shù)據(jù)?;靵y的數(shù)據(jù)也可能由于操作系統(tǒng)的BUG而寫入到一個(gè)SQLite的數(shù)據(jù)文件中。對(duì)于這些情況,SQLite無能為力。

9.5 刪除掉或更名了“hot”日志文件

如果掉電或系統(tǒng)崩潰導(dǎo)致留下了一個(gè)”hot”日志文件在磁盤上。實(shí)際上,原來的數(shù)據(jù)文件再加上留下來的“hot“日志文件, 是SQLite下回打開時(shí)發(fā)生回滾使用的,這可以恢復(fù)SQLite數(shù)據(jù)的正常狀態(tài)(節(jié)4.2)。SQLite會(huì)在數(shù)據(jù)庫所在同一目錄下用打開的文件名來尋找可能存在的”hot”日志文件。如果數(shù)據(jù)文件或者日志文件被移動(dòng)或者改名,或者刪除掉了,那么這些日志文件將不會(huì)被回滾,數(shù)據(jù)庫也就可能損壞,無法使用了。

我們常懷疑SQLite發(fā)生的恢復(fù)失敗的例子是這樣的:停電了,之后電又恢復(fù)了。一個(gè)好心的用戶或者系統(tǒng)管理管理員開始查看磁盤損壞。他們看到名為"important.data"數(shù)據(jù)庫文件,或許類似的文件。但由于停電,這里也同樣有一個(gè)日志文件名為"important.data-journal".這個(gè)用戶刪除了這個(gè)“hot”日志文件,認(rèn)為他是清理系統(tǒng)。那于這種情況,除了進(jìn)行用戶培訓(xùn),沒有其他辦法。

如果有多個(gè)聯(lián)接(硬或者符號(hào)聯(lián)接)指向一個(gè)數(shù)據(jù)文件,這個(gè)日志文件會(huì)以被打開的聯(lián)接文件名相關(guān)來創(chuàng)建的。如果系統(tǒng)崩潰之后,數(shù)據(jù)庫以一個(gè)新的聯(lián)接重新打開,這個(gè)“hot”日志文件就不會(huì)被找到,數(shù)據(jù)也不會(huì)發(fā)生回滾。

有時(shí),電源問題會(huì)導(dǎo)致文件系統(tǒng)出現(xiàn)毛病,如最新修改的文件名被丟失了,并會(huì)轉(zhuǎn)移至類似于"/lost+found"這樣的目錄中。當(dāng)這種情況發(fā)生的時(shí)候,這個(gè)hot日志文件就不會(huì)被找到,同樣恢復(fù)也不會(huì)發(fā)生。SQLite在同步一個(gè)日志文件時(shí)通過打開并同步日志文件所在目錄來嘗試阻止這類事件發(fā)生。然后,轉(zhuǎn)移文件到"/lost+found"可能會(huì)由不相關(guān)的其他進(jìn)程在相同的目錄中產(chǎn)生與主數(shù)據(jù)庫文件名相同的不相關(guān)文件。既然這都是SQLite所無法控制,所以SQLite沒有什么好辦法。如果你運(yùn)行在一種易導(dǎo)致名稱空間沖突的文件系統(tǒng)上,那么你最好把每一個(gè)SQLite的數(shù)據(jù)文件放在你私有的子目錄中。

10.0 總結(jié)及未來的路

即使到了現(xiàn)在,還是有人發(fā)現(xiàn)了一些關(guān)于原子提交機(jī)制失敗模式,開發(fā)者不得不為此做一些補(bǔ)丁。這樣的事情發(fā)生得越來越少了,失敗模型也變得越來越模糊了。但如果就認(rèn)為SQLite的原子提交邏輯是沒有任何bug,那是相當(dāng)愚昧的。開發(fā)者承諾將盡可能快的修復(fù)被發(fā)現(xiàn)的bug。

開發(fā)者同時(shí)在考慮新的優(yōu)化提交機(jī)制的辦法。當(dāng)前的linux,macOSX,win32的VFS實(shí)現(xiàn)使用這些系統(tǒng)之上的一些悲觀設(shè)定?;蛟S在與一些了解這些系統(tǒng)如何工作的專家交流之后,我們或許可能放松一些這些系統(tǒng)上的設(shè)定,使其跑得更快些。特別的,我們懷疑的大部分現(xiàn)代文件系統(tǒng)現(xiàn)在已經(jīng)展現(xiàn)安全追加特性,或許他們都已經(jīng)支持了扇區(qū)的原子操作。但是除非這些得到明確,SQLite仍將采用更安全、保守的方法,作最壞的打算。

關(guān)于SQLite原子提交的原理是什么就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI