一、簡(jiǎn)介:
盡管SQLite的數(shù)據(jù)庫(kù)是由單一文件構(gòu)成,然而事實(shí)上在SQLite運(yùn)行時(shí)卻存在著一些隱含的臨時(shí)文件,這些臨時(shí)文件是出于不同的目的而存在的,對(duì)于開(kāi)發(fā)者而言,它們是透明的,因此在開(kāi)發(fā)的過(guò)程中我們并不需要關(guān)注它們的存在。盡管如此,如果能對(duì)這些臨時(shí)文件的產(chǎn)生機(jī)制和應(yīng)用場(chǎng)景有著很好的理解,那么對(duì)我們今后應(yīng)用程序的優(yōu)化和維護(hù)都是極有幫助的。在SQLite中主要產(chǎn)生以下七種臨時(shí)文件,如:
1). 回滾日志。
2). 主數(shù)據(jù)庫(kù)日志。
3). SQL語(yǔ)句日志。
4). 臨時(shí)數(shù)據(jù)庫(kù)文件。
5). 視圖和子查詢(xún)的臨時(shí)持久化文件。
6). 臨時(shí)索引文件。
7). VACUUM命令使用的臨時(shí)數(shù)據(jù)庫(kù)文件。
二、具體說(shuō)明:
1. 回滾日志:
SQLite為了保證事物的原子性提交和回滾,在事物開(kāi)始時(shí)創(chuàng)建了該臨時(shí)文件。此文件始終位于和數(shù)據(jù)庫(kù)文件相同的目錄下,其文件名格式為: 數(shù)據(jù)庫(kù)文件名 + "-journal"。換句話說(shuō),如果沒(méi)有該臨時(shí)文件的存在,當(dāng)程序運(yùn)行的系統(tǒng)出現(xiàn)任何故障時(shí),SQLite將無(wú)法保證事物的完整性,以及數(shù)據(jù)狀態(tài)的一致性。該文件在事物提交或回滾后將被立刻刪除。
在事物運(yùn)行期間,如果當(dāng)前主機(jī)因電源故障而宕機(jī),而此時(shí)由于回滾日志文件已經(jīng)保存在磁盤(pán)上,那么當(dāng)下一次程序啟動(dòng)時(shí),SQLite在打開(kāi)數(shù)據(jù)庫(kù)文件的過(guò)程中將會(huì)發(fā)現(xiàn)該臨時(shí)文件的存在,我們稱(chēng)這種日志文件為"Hot Journal"。SQLite會(huì)在成功打開(kāi)數(shù)據(jù)庫(kù)之前先基于該文件完成數(shù)據(jù)庫(kù)的恢復(fù)工作,以保證數(shù)據(jù)庫(kù)的數(shù)據(jù)回復(fù)到上一個(gè)事物開(kāi)始之前的狀態(tài)。
在SQLite中我們可以通過(guò)修改journal_mode pragma,而使SQLite對(duì)維護(hù)該文件采用不同的策略。缺省情況下該值為DELETE,即在事物結(jié)束后刪除日志文件。而PERSIST選項(xiàng)值將不會(huì)刪除日志文件,而是將回滾日志文件的頭部清零,從而避免了文件刪除所帶的磁盤(pán)開(kāi)銷(xiāo)。再有就是OFF選項(xiàng)值,該值將指示SQLite在開(kāi)始事物時(shí)不產(chǎn)生回滾日志文件,這樣一旦出現(xiàn)系統(tǒng)故障,SQLite也無(wú)法再保障數(shù)據(jù)庫(kù)數(shù)據(jù)的一致性。
2. 主數(shù)據(jù)庫(kù)日志:
在SQLite中,如果事物的操作作用于多個(gè)數(shù)據(jù)庫(kù),即通過(guò)ATTACH命令附加到當(dāng)前連接中的數(shù)據(jù)庫(kù),那么SQLite將生成主數(shù)據(jù)庫(kù)日志文件以保證事物產(chǎn)生的改變?cè)诙鄠€(gè)數(shù)據(jù)庫(kù)之間保持原子性。和回滾日志文件一樣,主數(shù)據(jù)庫(kù)日志文件也位于當(dāng)前連接中主數(shù)據(jù)庫(kù)文件所處的目錄內(nèi),其文件名格式為:主數(shù)據(jù)庫(kù)文件名 + 隨機(jī)的后綴。在該文件中,將包含所有當(dāng)前事物將會(huì)改變的Attached數(shù)據(jù)庫(kù)的名字。在事物被提交之后,此文件亦被SQLite隨之刪除。
主數(shù)據(jù)庫(kù)日志文件只有在某一事物同時(shí)操作多個(gè)數(shù)據(jù)庫(kù)時(shí)(主數(shù)據(jù)庫(kù)和Attached數(shù)據(jù)庫(kù))才有可能被創(chuàng)建。通過(guò)該文件,SQLite可以實(shí)現(xiàn)跨多個(gè)數(shù)據(jù)庫(kù)的事物原子性,否則,只能簡(jiǎn)單的保證每個(gè)單一的數(shù)據(jù)庫(kù)內(nèi)的狀態(tài)一致性。換句話說(shuō),如果該事物在執(zhí)行的過(guò)程中出現(xiàn)系統(tǒng)崩潰或主機(jī)宕機(jī)的現(xiàn)象,在進(jìn)行數(shù)據(jù)恢復(fù)時(shí),若沒(méi)有該文件的存在,將會(huì)導(dǎo)致部分SQLite數(shù)據(jù)庫(kù)處于提交狀態(tài),而另外一部分則處于回滾狀態(tài),因此該事物的一致性將被打破。
3. SQL語(yǔ)句日志:
在一個(gè)較大的事物中,SQLite為了保證部分?jǐn)?shù)據(jù)在出現(xiàn)錯(cuò)誤時(shí)可以被正?;貪L,所以在事物開(kāi)始時(shí)創(chuàng)建了SQL語(yǔ)句日志文件。比如,update語(yǔ)句修改了前50條數(shù)據(jù),然而在修改第51條數(shù)據(jù)時(shí)發(fā)現(xiàn)該操作將會(huì)破壞某字段的唯一性約束,最終SQLite將不得不通過(guò)該日志文件回滾已經(jīng)修改的前50條數(shù)據(jù)。
SQL語(yǔ)句日志文件只有在INSERT或UPDATE語(yǔ)句修改多行記錄時(shí)才有可能被創(chuàng)建,與此同時(shí),這些操作還極有可能會(huì)打破某些約束并引發(fā)異常。但是如果INSERT或UPDATE語(yǔ)句沒(méi)有被包含在BEGIN...COMMIT中,同時(shí)也沒(méi)有任何其它的SQL語(yǔ)句正在當(dāng)前的連接上運(yùn)行,在這種情況下,SQLite將不會(huì)創(chuàng)建SQL語(yǔ)句日志文件,而是簡(jiǎn)單的通過(guò)回滾日志來(lái)完成部分?jǐn)?shù)據(jù)的UNDO操作。
和上面兩種臨時(shí)文件不同的是,SQL語(yǔ)句日志文件并不一定要存儲(chǔ)在和數(shù)據(jù)庫(kù)文件相同的目錄下,其文件名也是隨機(jī)生成。該文件所占用的磁盤(pán)空間需要視UPDATE或INSERT語(yǔ)句將要修改的記錄數(shù)量而定。在事物結(jié)束后,該文件將被自動(dòng)刪除。
4. 臨時(shí)數(shù)據(jù)庫(kù)文件:
當(dāng)使用"CREATE TEMP TABLE"語(yǔ)法創(chuàng)建臨時(shí)數(shù)據(jù)表時(shí),該數(shù)據(jù)表僅在當(dāng)前連接內(nèi)可見(jiàn),在當(dāng)前連接被關(guān)閉后,臨時(shí)表也隨之消失。然而在生命期內(nèi),臨時(shí)表將連同其相關(guān)的索引和視圖均會(huì)被存儲(chǔ)在一個(gè)臨時(shí)的數(shù)據(jù)庫(kù)文件之內(nèi)。該臨時(shí)文件是在第一次執(zhí)行"CREATE TEMP TABLE"時(shí)即被創(chuàng)建的,在當(dāng)前連接被關(guān)閉后,該文件亦將被自動(dòng)刪除。最后需要說(shuō)明的是,臨時(shí)數(shù)據(jù)庫(kù)不能被執(zhí)行DETACH命令,同時(shí)也不能被其它進(jìn)程執(zhí)行ATTACH命令。
5. 視圖和子查詢(xún)的臨時(shí)持久化文件:
在很多包含子查詢(xún)的查詢(xún)中,SQLite的執(zhí)行器會(huì)將該查詢(xún)語(yǔ)句拆分為多個(gè)獨(dú)立的SQL語(yǔ)句,同時(shí)將子查詢(xún)的結(jié)果持久化到臨時(shí)文件中,之后在基于該臨時(shí)文件中的數(shù)據(jù)與外部查詢(xún)進(jìn)行關(guān)聯(lián),因此我們可以稱(chēng)其為物化子查詢(xún)。通常而言,SQLite的優(yōu)化器會(huì)盡力避免子查詢(xún)的物化行為,但是在有些時(shí)候該操作是無(wú)法避免的。該臨時(shí)文件所占用的磁盤(pán)空間需要依賴(lài)子查詢(xún)檢索出的數(shù)據(jù)數(shù)量,在查詢(xún)結(jié)束后,該文件將被自動(dòng)刪除。見(jiàn)如下示例:
復(fù)制代碼 代碼如下:
SELECT * FROM ex1 WHERE ex1.a IN (SELECT b FROM ex2);
在上面的查詢(xún)語(yǔ)句中,子查詢(xún)SELECT b FROM ex2的結(jié)果將會(huì)被持久化到臨時(shí)文件中,外部查詢(xún)?cè)谶\(yùn)行時(shí)將會(huì)為每一條記錄去檢查該臨時(shí)文件,以判斷當(dāng)前記錄是否出現(xiàn)在臨時(shí)文件中,如果是則輸出當(dāng)前記錄。顯而易見(jiàn)的是,以上的行為將會(huì)產(chǎn)生大量的IO操作,從而顯著的降低了查詢(xún)的執(zhí)行效率,為了避免臨時(shí)文件的生成,我們可以將上面的查詢(xún)語(yǔ)句改為:
復(fù)制代碼 代碼如下:
SELECT * FROM ex1 WHERE EXISTS(SELECT 1 FROM ex2 WHERE ex2.b=ex1.a);
對(duì)于如下查詢(xún)語(yǔ)句,如果SQLite不做任何智能的rewrite操作,該查詢(xún)中的子查詢(xún)也將會(huì)被持久化到臨時(shí)文件中,如:
復(fù)制代碼 代碼如下:
SELECT * FROM ex1 JOIN (SELECT b FROM ex2) AS t ON t.b=ex1.a;
在SQLite自動(dòng)將其修改為下面的寫(xiě)法后,將不會(huì)再生成臨時(shí)文件了,如:
復(fù)制代碼 代碼如下:
SELECT ex1.*, ex2.b FROM ex1 JOIN ex2 ON ex2.b=ex1.a;
6. 臨時(shí)索引文件:
當(dāng)查詢(xún)語(yǔ)句包含以下SQL從句時(shí),SQLite為存儲(chǔ)中間結(jié)果而創(chuàng)建了臨時(shí)索引文件,如:
1). ORDER BY或GROUP BY從句。
2). 聚集查詢(xún)中的DISTINCT關(guān)鍵字。
3). 由UNION、EXCEPT和INTERSECT連接的多個(gè)SELECT查詢(xún)語(yǔ)句。
需要說(shuō)明的是,如果在指定的字段上已經(jīng)存在了索引,那么SQLite將不會(huì)再創(chuàng)建該臨時(shí)索引文件,而是通過(guò)直接遍歷索引來(lái)訪問(wèn)數(shù)據(jù)并提取有用信息。如果沒(méi)有索引,則需要將排序的結(jié)果存儲(chǔ)在臨時(shí)索引文件中以供后用。該臨時(shí)文件所占用的磁盤(pán)空間需要依賴(lài)排序數(shù)據(jù)的數(shù)量,在查詢(xún)結(jié)束后,該文件被自動(dòng)刪除。
7. VACUUM命令使用的臨時(shí)數(shù)據(jù)庫(kù)文件:
VACUUM命令在工作時(shí)將會(huì)先創(chuàng)建一個(gè)臨時(shí)文件,然后再將重建的整個(gè)數(shù)據(jù)庫(kù)寫(xiě)入到該臨時(shí)文件中。之后再將臨時(shí)文件中的內(nèi)容拷貝回原有的數(shù)據(jù)庫(kù)文件中,最后刪除該臨時(shí)文件。
該臨時(shí)文件所占用的磁盤(pán)空間不會(huì)超過(guò)原有文件的尺寸。
三、相關(guān)的編譯時(shí)參數(shù)和指令:
對(duì)于SQLite來(lái)說(shuō),回滾日志、主數(shù)據(jù)庫(kù)日志和SQL語(yǔ)句日志文件在需要的時(shí)候SQLite都會(huì)將它們寫(xiě)入磁盤(pán)文件,但是對(duì)于其它類(lèi)型的臨時(shí)文件,SQLite是可以將它們存放在內(nèi)存中以取代磁盤(pán)文件的,這樣在執(zhí)行的過(guò)程中就可以減少大量的IO操作了。要完成該優(yōu)化主要依賴(lài)于以下三個(gè)因素:
1. 編譯時(shí)參數(shù)SQLITE_TEMP_STORE:
該參數(shù)是源代碼中的宏定義(#define),其取值范圍是0到3(缺省值為1),見(jiàn)如下說(shuō)明:
1). 等于0時(shí),臨時(shí)文件總是存儲(chǔ)在磁盤(pán)上,而不會(huì)考慮temp_store pragma指令的設(shè)置。
2). 等于1時(shí),臨時(shí)文件缺省存儲(chǔ)在磁盤(pán)上,但是該值可以被temp_store pragma指令覆蓋。
3). 等于2時(shí),臨時(shí)文件缺省存儲(chǔ)在內(nèi)存中,但是該值可以被temp_store pragma指令覆蓋。
4). 等于3時(shí),臨時(shí)文件總是存儲(chǔ)在內(nèi)存中,而不會(huì)考慮temp_store pragma指令的設(shè)置。
2. 運(yùn)行時(shí)指令temp_store pragma:
該指令的取值范圍是0到2(缺省值為0),在程序運(yùn)行時(shí)該指令可以被動(dòng)態(tài)的設(shè)置,見(jiàn)如下說(shuō)明:
1). 等于0時(shí),臨時(shí)文件的存儲(chǔ)行為完全由SQLITE_TEMP_STORE編譯期參數(shù)確定。
2). 等于1時(shí),如果編譯期參數(shù)SQLITE_TEMP_STORE指定使用內(nèi)存存儲(chǔ)臨時(shí)文件,那么該指令將覆蓋這一行為,使用磁盤(pán)存儲(chǔ)。
2). 等于2時(shí),如果編譯期參數(shù)SQLITE_TEMP_STORE指定使用磁盤(pán)存儲(chǔ)臨時(shí)文件,那么該指令將覆蓋這一行為,使用內(nèi)存存儲(chǔ)。
3. 臨時(shí)文件的大?。?/p>
對(duì)于以上兩個(gè)參數(shù),都有參數(shù)值表示缺省情況是存儲(chǔ)在內(nèi)存中的,只有當(dāng)臨時(shí)文件的大小超過(guò)一定的閾值后才會(huì)根據(jù)一定的算法,將部分?jǐn)?shù)據(jù)寫(xiě)入到磁盤(pán)中,以免臨時(shí)文件占用過(guò)多的內(nèi)存而影響其它程序的執(zhí)行效率。
最后在重新贅述一遍,SQLITE_TEMP_STORE編譯期參數(shù)和temp_store pragma運(yùn)行時(shí)指令只會(huì)影響除回滾日志和主數(shù)據(jù)庫(kù)日志之外的其它臨時(shí)文件的存儲(chǔ)策略。換句話說(shuō),回滾日志和主數(shù)據(jù)庫(kù)日志將總是將數(shù)據(jù)寫(xiě)入磁盤(pán),而不會(huì)關(guān)注以上兩個(gè)參數(shù)的值。
四、其它優(yōu)化策略:
在SQLite中由于采用了Page Cache的緩沖優(yōu)化機(jī)制,因此即便臨時(shí)文件被指定存儲(chǔ)在磁盤(pán)上,也只有當(dāng)該文件的大小增長(zhǎng)到一定的尺寸后才有可能被SQLite刷新到磁盤(pán)文件上,在此之前它們?nèi)詫Ⅰv留在內(nèi)存中。這就意味著對(duì)于大多數(shù)場(chǎng)景,如果臨時(shí)表和臨時(shí)索引的數(shù)據(jù)量相對(duì)較少,那么它們是不會(huì)被寫(xiě)到磁盤(pán)中的,當(dāng)然也就不會(huì)有IO事件發(fā)生。只有當(dāng)它們?cè)鲩L(zhǎng)到內(nèi)存不能容納的時(shí)候才會(huì)被刷新到磁盤(pán)文件中的。其中SQLITE_DEFAULT_TEMP_CACHE_SIZE編譯期參數(shù)可以用于指定臨時(shí)表和索引在占用多少Cache Page時(shí)才需要被刷新到磁盤(pán)文件,該參數(shù)的缺省值為500頁(yè)。
您可能感興趣的文章:- SQLite教程(一):SQLite數(shù)據(jù)庫(kù)介紹
- SQLite教程(二):C/C++接口簡(jiǎn)介
- SQLite教程(三):數(shù)據(jù)表和視圖簡(jiǎn)介
- SQLite教程(四):內(nèi)置函數(shù)
- SQLite教程(五):索引和數(shù)據(jù)分析/清理
- SQLite教程(五):數(shù)據(jù)庫(kù)和事務(wù)
- SQLite教程(六):表達(dá)式詳解
- SQLite教程(七):數(shù)據(jù)類(lèi)型詳解
- SQLite教程(八):命令行工具介紹
- SQLite教程(九):在線備份
- SQLite教程(十):內(nèi)存數(shù)據(jù)庫(kù)和臨時(shí)數(shù)據(jù)庫(kù)