SQLite 的 獨特特色 之一,就是資料庫由單一磁碟檔案組成。這簡化了 SQLite 的使用,因為移動或備份資料庫就像複製單一檔案一樣簡單。這也讓 SQLite 適合用作 應用程式檔案格式。但儘管完整的資料庫儲存在單一磁碟檔案中,SQLite 在處理資料庫的過程中仍會使用許多暫存檔。
本文說明 SQLite 建立和使用的各種暫存檔。它說明檔案建立和刪除的時間、用途、重要性,以及如何在建立暫存檔很昂貴的系統中避免使用暫存檔。
SQLite 使用暫存檔案的方式不算是 SQLite 與應用程式之間合約的一部分。此文件中資訊為此文件撰寫或最後更新時 SQLite 運作方式的正確說明。但無法保證未來版本的 SQLite 會以相同方式使用暫存檔案。未來版本的 SQLite 可能會使用新類型的暫存檔案,而某些目前暫存檔案的使用方式可能會在未來版本中終止。
SQLite 目前使用九種不同的暫存檔案類型
後續內容提供每種這些暫存檔案類型的其他資訊。
回滾記錄檔是 SQLite 中用於實作原子提交和回滾功能的暫存檔案。(有關其運作方式的詳細說明,請參閱標題為 SQLite 中的原子提交 的個別文件。)回滾記錄檔始終位於與資料庫檔案相同的目錄中,且名稱與資料庫檔案相同,但附加 8 個字元「-journal」。回滾記錄檔通常在交易首次啟動時建立,並通常在交易提交或回滾時刪除。回滾記錄檔檔案對於實作 SQLite 的原子提交和回滾功能至關重要。沒有回滾記錄檔,SQLite 將無法回滾不完整的交易,而且如果在交易過程中發生當機或斷電,則整個資料庫很可能會在沒有回滾記錄檔的情況下損毀。
回滾日誌通常在交易的開始和結束時分別建立和銷毀。但此規則有例外。
如果在交易過程中發生當機或斷電,則回滾日誌檔案會留在磁碟上。下次其他應用程式嘗試開啟資料庫檔案時,它會注意到已放棄的回滾日誌的存在(我們在這種情況下稱之為「熱日誌」),並使用日誌中的資訊將資料庫還原到不完整交易開始前的狀態。這是 SQLite 實作原子提交的方式。
如果應用程式使用 pragma 將 SQLite 設定為獨佔鎖定模式
PRAGMA locking_mode=EXCLUSIVE;
SQLite 會在獨佔鎖定模式工作階段中的第一個交易開始時建立新的回滾日誌。但在交易結束時,它不會刪除回滾日誌。回滾日誌可能會被截斷,或其標頭可能會被歸零(取決於您使用的 SQLite 版本),但回滾日誌不會被刪除。回滾日誌不會被刪除,直到退出獨佔存取模式為止。
回滾日誌的建立與刪除也由 journal_mode pragma 進行變更。預設的日誌模式為 DELETE,這是如上所述在每個交易結束時刪除回滾日誌檔案的預設行為。PERSIST 日誌模式會放棄刪除日誌檔案,而改用零覆寫回滾日誌標頭,這會防止其他程序回滾日誌,因此與刪除日誌檔案具有相同的效果,但無需實際從磁碟中移除檔案的開銷。換句話說,日誌模式 PERSIST 會表現出與在 EXCLUSIVE 鎖定模式中所見相同的行為。OFF 日誌模式會導致 SQLite 完全略過回滾日誌。換句話說,如果日誌模式設定為 OFF,則永遠不會寫入回滾日誌。OFF 日誌模式會停用 SQLite 的原子提交和回滾功能。設定 OFF 日誌模式時,ROLLBACK 指令不可用。如果使用 OFF 日誌模式的交易過程中發生崩潰或斷電,則無法復原,而且資料庫檔案可能會損毀。MEMORY 日誌模式會將回滾日誌儲存在記憶體中,而不是儲存在磁碟中。當日誌模式為 MEMORY 時,ROLLBACK 指令仍然有效,但由於磁碟中沒有可供復原的檔案,因此使用 MEMORY 日誌模式的交易過程中發生崩潰或斷電,可能會導致資料庫損毀。
當 SQLite 在 WAL 模式 下執行時,會使用預先寫入記錄或 WAL 檔案來取代回滾記錄檔。與回滾記錄檔一樣,WAL 檔案的目的是實作原子提交和回滾。WAL 檔案始終位於與資料庫檔案相同的目錄中,且名稱與資料庫檔案相同,但附加了 4 個字元「-wal」。WAL 檔案會在開啟資料庫的第一個連線時建立,並通常在關閉資料庫的最後一個連線時移除。但是,如果最後一個連線沒有正常關閉,WAL 檔案將保留在檔案系統中,並會在下次開啟資料庫時自動清除。
在 WAL 模式 下執行時,所有與相同資料庫檔案關聯的 SQLite 資料庫連線需要共用一些記憶體,作為 WAL 檔案的索引。在大部分的實作中,這個共用記憶體是透過對為此唯一目的建立的檔案呼叫 mmap() 來實作的:共用記憶體檔案。共用記憶體檔案(如果存在)位於與資料庫檔案相同的目錄中,且名稱與資料庫檔案相同,但附加了 4 個字元「-shm」。共用記憶體檔案只會在 WAL 模式下執行時存在。
共用記憶體檔案不包含任何持續性內容。共用記憶體檔案的唯一目的是提供一個共用記憶體區塊,供所有存取相同資料庫(在 WAL 模式下)的多個處理程序使用。如果 VFS 能夠提供存取共用記憶體的替代方法,則可能會使用該替代方法,而不是共用記憶體檔案。例如,如果 PRAGMA locking_mode 設為 EXCLUSIVE(表示只有一個處理程序能夠存取資料庫檔案),則共用記憶體會從堆疊中配置,而不是從共用記憶體檔案中配置,且共用記憶體檔案永遠不會建立。
共享記憶體檔案與其相關的 WAL 檔案具有相同的生命週期。在建立 WAL 檔案時會建立共享記憶體檔案,並在刪除 WAL 檔案時刪除共享記憶體檔案。在 WAL 檔案復原期間,會根據正在復原的 WAL 檔案內容從頭開始重新建立共享記憶體檔案。
當單一交易對已使用 ATTACH 陳述式新增至單一 資料庫連線 的多個資料庫進行變更時,超級日誌檔案會用於原子提交程序的一部分。超級日誌檔案始終位於主資料庫檔案的相同目錄中(主資料庫檔案是在原始 sqlite3_open()、sqlite3_open16() 或 sqlite3_open_v2() 呼叫中識別的資料庫,該呼叫建立了 資料庫連線),並具有隨機字尾。超級日誌檔案包含交易期間變更的所有不同附加輔助資料庫的名稱。當刪除超級日誌檔案時,多資料庫交易會提交。請參閱標題為 SQLite 中的原子提交 的文件,以取得更多詳細資訊。
如果沒有超級日誌,多資料庫交易的交易提交會對每個資料庫個別進行原子提交,但不會跨所有資料庫進行原子提交。換句話說,如果提交在中間因崩潰或斷電而中斷,則對其中一個資料庫的變更可能會完成,而對另一個資料庫的變更可能會回滾。超級日誌會導致所有資料庫中的所有變更一起回滾或提交。
超級日誌檔案僅建立於涉及多個資料庫檔案的 COMMIT 作業,其中至少兩個資料庫符合所有下列需求
這表示當資料庫檔案關閉同步或使用關閉、記憶體或 WAL 的日誌模式時,SQLite 交易在多個資料庫檔案中不會跨資料庫原子化。對於同步關閉和日誌模式關閉和記憶體,如果交易提交因斷電而中斷,資料庫通常會損毀。對於 WAL 模式,個別資料庫檔案會在斷電時跨資料庫原子化更新,但在多檔案交易的情況下,某些檔案可能會在來電後回滾,而其他檔案則會向前滾動。
陳述式日誌檔案用於在較大交易中回滾單一陳述式的部分結果。例如,假設 UPDATE 陳述式將嘗試修改資料庫中的 100 列。但在修改前 50 列後,UPDATE 會遇到應封鎖整個陳述式的約束式違規。陳述式日誌用於復原前 50 列變更,以便將資料庫回復到陳述式開始時的狀態。
陳述式日誌僅建立於可能會變更資料庫多列且可能會在觸發器中遇到約束式或 RAISE 例外狀況,因此需要復原部分結果的 UPDATE 或 INSERT 陳述式。如果 UPDATE 或 INSERT 未包含在 BEGIN...COMMIT 中,且同一個資料庫連線中沒有其他活動陳述式,則不會建立陳述式日誌,因為可以改用一般回滾日誌。如果使用替代 衝突解決演算法,也會省略陳述式日誌。例如
UPDATE OR FAIL ... UPDATE OR IGNORE ... UPDATE OR REPLACE ... UPDATE OR ROLLBACK ... INSERT OR FAIL ... INSERT OR IGNORE ... INSERT OR REPLACE ... INSERT OR ROLLBACK ... REPLACE INTO ....
報表日誌會給予一個隨機名稱,不一定與主資料庫在同一個目錄,且會在交易結束時自動刪除。報表日誌的大小與執行 UPDATE 或 INSERT 陳述式所實作的變更大小成正比,而該陳述式會導致建立報表日誌。
使用「CREATE TEMP TABLE」語法建立的資料表只會顯示在「CREATE TEMP TABLE」陳述式最初評估的資料庫連線中。這些 TEMP 資料表,以及任何關聯的索引、觸發器和檢視,會一起儲存在一個獨立的暫時資料庫檔案中,該檔案會在看到第一個「CREATE TEMP TABLE」陳述式時建立。這個獨立的暫時資料庫檔案也有關聯的回滾日誌。用於儲存 TEMP 資料表的暫時資料庫檔案會在使用sqlite3_close()關閉資料庫連線時自動刪除。
TEMP 資料庫檔案非常類似於使用ATTACH陳述式新增的輔助資料庫檔案,但有一些特殊屬性。TEMP 資料庫會在關閉資料庫連線時自動刪除。TEMP 資料庫總是使用synchronous=OFF和journal_mode=PERSISTPRAGMA 設定。此外,TEMP 資料庫無法與DETACH搭配使用,其他程序也無法ATTACHTEMP 資料庫。
與 TEMP 資料庫及其回滾日誌關聯的暫時檔案只有在應用程式使用「CREATE TEMP TABLE」陳述式時才會建立。
包含子查詢的查詢有時必須個別評估子查詢,並將結果儲存在暫時表格中,然後使用暫時表格的內容來評估外部查詢。我們稱此為「實體化」子查詢。SQLite 中的查詢最佳化器會嘗試避免實體化,但有時並不容易避免。實體化所建立的暫時表格會個別儲存在其自己的暫時檔案中,並會在查詢結束時自動刪除。當然,這些暫時表格的大小取決於子查詢實體化中的資料量。
IN 運算子右邊的子查詢通常必須實體化。例如
SELECT * FROM ex1 WHERE ex1.a IN (SELECT b FROM ex2);
在上面的查詢中,子查詢「SELECT b FROM ex2」會被評估,而其結果會儲存在暫時表格中(實際上是暫時索引),讓你可以使用簡單的二元搜尋來判斷值 ex2.b 是否存在。建立此表格後,會執行外部查詢,並針對每個預期的結果列檢查 ex1.a 是否包含在暫時表格中。只有在檢查結果為真時,才會輸出該列。
若要避免建立暫時表格,可以將查詢改寫如下
SELECT * FROM ex1 WHERE EXISTS(SELECT 1 FROM ex2 WHERE ex2.b=ex1.a);
較新的 SQLite 版本(版本 3.5.4 2007-12-14)及更新版本)如果 ex2.b 欄位上有索引,會自動執行此改寫。
如果 IN 運算子的右邊可以是值清單,如下所示
SELECT * FROM ex1 WHERE a IN (1,2,3);
IN 右邊的值清單會被視為必須實體化的子查詢。換句話說,前一個陳述式會像這樣運作
SELECT * FROM ex1 WHERE a IN (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3);
當 IN 運算子的右邊是值清單時,會永遠使用暫時索引來儲存右邊的值。
當子查詢出現在 SELECT 陳述式的 FROM 子句中時,也可能需要實體化。例如
SELECT * FROM ex1 JOIN (SELECT b FROM ex2) AS t ON t.b=ex1.a;
根據查詢,SQLite 可能需要將「(SELECT b FROM ex2)」子查詢實體化成暫時表格,然後執行 ex1 與暫時表格之間的聯結。查詢最佳化程式會嘗試透過「扁平化」查詢來避免這個情況。在先前的範例中,查詢可以扁平化,而 SQLite 會自動將查詢轉換成
SELECT ex1.*, ex2.b FROM ex1 JOIN ex2 ON ex2.b=ex1.a;
較複雜的查詢可能無法使用查詢扁平化來避免暫時表格。查詢是否可以扁平化取決於子查詢或外部查詢是否包含聚合函數、ORDER BY 或 GROUP BY 子句、LIMIT 子句等因素。查詢何時可以或不可以扁平化的規則非常複雜,而且超出本文件範圍。
SQLite 可能使用暫時索引來實作 SQL 語言功能,例如
每個暫時索引都儲存在自己的暫時檔案中。暫時索引的暫時檔案會在使用它的陳述式結束時自動刪除。
SQLite 致力於使用已存在的索引來實作 ORDER BY 子句。如果已存在適當的索引,SQLite 會瀏覽索引,而不是基礎表格,來擷取所要求的資訊,並因此讓列以所需的順序輸出。但如果 SQLite 找不到適當的索引,它會評估查詢並將每列儲存在暫時索引中,其資料為列資料,而其鍵為 ORDER BY 詞彙。在評估查詢後,SQLite 會返回並從頭到尾瀏覽暫時索引,以按所需順序輸出列。
SQLite 透過按照 GROUP BY 項目建議的順序對輸出列進行排序來實作 GROUP BY。每個輸出列都會與前一個輸出列進行比較,以查看是否開始一個新的「群組」。按照 GROUP BY 項目進行排序的方式與按照 ORDER BY 項目進行排序的方式完全相同。如果可能,會使用現有的索引,但如果沒有適當的索引可用,則會建立暫時索引。
透過在暫時檔案中建立暫時索引並將每個結果列儲存在該索引中來實作聚合查詢上的 DISTINCT 關鍵字。當計算新的結果列時,會檢查它們是否已存在於暫時索引中,如果存在,則會捨棄新的結果列。
透過在暫時檔案中建立暫時索引並將左子查詢和右子查詢的結果儲存在暫時索引中,捨棄重複項,來實作複合查詢的 UNION 算子。在評估兩個子查詢後,從頭到尾遍歷暫時索引以產生最終輸出。
透過在暫時檔案中建立暫時索引,將左子查詢的結果儲存在此暫時索引中,然後從暫時索引中移除右子查詢的結果,最後從頭到尾遍歷索引以取得最終輸出,來實作複合查詢的 EXCEPT 算子。
透過在兩個不同的暫時檔案中建立兩個獨立的暫時索引來實作複合查詢的 INTERSECT 算子。左子查詢和右子查詢分別評估到獨立的暫時索引中。然後,同時遍歷這兩個索引,並輸出同時出現在兩個索引中的項目。
請注意,用於複合查詢的 UNION ALL 運算子本身不使用暫時索引(當然,UNION ALL 的右側和左側子查詢可能會根據其組成方式使用暫時索引。)
VACUUM 指令透過建立一個暫時檔案,然後將整個資料庫重建到該暫時檔案中來運作。接著,暫時檔案的內容會複製回原始資料庫檔案,而暫時檔案會被刪除。
VACUUM 指令建立的暫時檔案只會存在於指令本身的執行期間。暫時檔案的大小不會大於原始資料庫。
與交易控制相關的暫時檔案,也就是回滾日誌、超級日誌、先行寫入日誌 (WAL) 檔案和共用記憶體檔案,總是寫入磁碟。但是其他類型的暫時檔案可能只儲存在記憶體中,而不會寫入磁碟。除了回滾、超級和陳述日誌之外,其他暫時檔案是否寫入磁碟或只儲存在記憶體中,取決於SQLITE_TEMP_STORE 編譯時期參數、temp_store pragma 和暫時檔案的大小。
SQLITE_TEMP_STORE 編譯時期參數是一個 #define,其值是一個介於 0 到 3(含)之間的整數。SQLITE_TEMP_STORE 編譯時期參數的意義如下
編譯時間參數 SQLITE_TEMP_STORE 的預設值為 1,表示將暫存檔案儲存在磁碟上,但提供使用 temp_store pragma 來覆寫行為的選項。
temp_store pragma 有一個整數值,也會影響暫存檔案儲存位置的決定。temp_store pragma 的值具有下列意義:
temp_store pragma 的預設設定為 0,表示遵循編譯時間參數 SQLITE_TEMP_STORE 的建議。
重申一下,SQLITE_TEMP_STORE 編譯時間參數和 temp_store pragma 僅影響回滾日誌和超級日誌以外的暫時檔案。無論 SQLITE_TEMP_STORE 編譯時間參數和 temp_store pragma 的設定為何,回滾日誌和超級日誌總是寫入磁碟。
SQLite 使用最近讀寫的資料庫頁面頁面快取。此頁面快取不僅用於主資料庫檔案,也用於暫時索引和儲存在暫時檔案中的資料表。如果 SQLite 需要使用暫時索引或資料表,且 SQLITE_TEMP_STORE 編譯時間參數和 temp_store pragma 設定為將暫時資料表和索引儲存在磁碟上,資訊仍會最初儲存在頁面快取中的記憶體中。暫時檔案不會開啟,且資訊不會真正寫入磁碟,直到頁面快取已滿。
這表示在許多暫時資料表和索引很小的常見情況下 (小到足以放入頁面快取中),不會建立暫時檔案,也不會發生磁碟 I/O。只有當暫時資料變大到無法放入 RAM 時,資訊才會溢位到磁碟。
每個暫時資料表和索引都有自己的頁面快取,可以儲存由 SQLITE_DEFAULT_TEMP_CACHE_SIZE 編譯時間參數所決定的最大資料庫頁面數量。(預設值為 500 個頁面。)頁面快取中資料庫頁面的最大數量對每個暫時資料表和索引都是相同的。此值無法在執行階段或以每個資料表或每個索引為基礎變更。每個暫時檔案都會取得自己的私人頁面快取,並有自己的 SQLITE_DEFAULT_TEMP_CACHE_SIZE 頁面限制。
建立暫存檔案的目錄或資料夾是由特定於作業系統的 VFS 所決定的。
在類 Unix 系統上,目錄會依下列順序進行搜尋
在 Windows 系統上,資料夾會依下列順序進行搜尋
此頁面最後修改於 2022-01-08 05:02:57 UTC