SQLite 3.5.0 版(2007-09-04)引進新的 OS 介面層,與所有先前版本的 SQLite 不相容。此外,一些現有的介面已廣泛化,可在一個程序中的所有資料庫連線中運作,而不再僅限於一個執行緒中的所有連線。本文的目的是詳細說明 3.5.0 的變更,以便先前版本的 SQLite 使用者判斷升級至較新版本需要多少工作(如果有)。
此處提供 SQLite 3.5.0 版變更的快速列舉。後續章節將更詳細地說明這些變更。
在這些變更中,只有 1a 和 2a 到 2c 在任何形式上是不相容的。但是,先前已自訂修改 SQLite 原始碼的使用者(例如,為嵌入式硬體新增自訂作業系統層),可能會發現這些變更影響較大。另一方面,這些變更的一個重要目標是讓自訂 SQLite 以供在不同的作業系統上使用變得更容易。
如果您的系統定義了 SQLite 的自訂作業系統介面,或者您正在使用未記錄的 sqlite3_os_switch() 介面,那麼您需要進行修改才能升級到 SQLite 版本 3.5.0。乍看之下這似乎很痛苦。但是當您仔細觀察時,您可能會發現您的變更變得更小,而且更容易透過新的 SQLite 介面來理解和管理。您的變更現在很可能會與 SQLite 混合無縫運作。您不再需要對 SQLite 原始碼進行任何變更。您的所有變更都可以透過應用程式碼進行,而且您可以連結到標準且未修改版本的 SQLite 混合。此外,作業系統介面層以前未記錄,現在是 SQLite 的官方支援介面。因此,您可以確定這將是一次性的變更,而且您的新後端將繼續在未來的 SQLite 版本中運作。
SQLite 的新作業系統介面建立在名為 sqlite3_vfs 的物件上。「vfs」代表「虛擬檔案系統」。sqlite3_vfs 物件基本上是一個結構,其中包含指向函式的指標,這些函式實作 SQLite 執行讀取和寫入資料庫時所需的原始磁碟 I/O 作業。在本文中,我們通常會將 sqlite3_vfs 物件稱為「VFS」。
SQLite 能夠同時使用多個 VFS。每個個別資料庫連線只會關聯到一個 VFS。但是如果您有多個資料庫連線,每個連線都可以關聯到不同的 VFS。
始終有一個預設的 VFS。舊式介面 sqlite3_open() 和 sqlite3_open16() 始終使用預設的 VFS。建立資料庫連線的新介面 sqlite3_open_v2() 允許您指定要使用的 VFS 名稱。
標準的 SQLite for Unix 或 Windows 建置附帶一個名為「unix」或「win32」的單一 VFS,視情況而定。這個 VFS 也是預設的。因此,如果您使用舊式開啟函式,所有內容都將繼續像以前一樣運作。變更在於應用程式現在可以靈活地新增新的 VFS 模組,以實作自訂的作業系統層。可以使用 sqlite3_vfs_register() API 告知 SQLite 一個或多個應用程式定義的 VFS 模組
int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt);
應用程式可以隨時呼叫 sqlite3_vfs_register(),不過當然在使用 VFS 之前需要先註冊。第一個引數是指向應用程式已準備好的自訂 VFS 物件的指標。第二個引數為 true,可將新的 VFS 設定為預設的 VFS,以便舊式的 sqlite3_open() 和 sqlite3_open16() API 使用它。如果新的 VFS 不是預設的,則您可能必須使用新的 sqlite3_open_v2() API 才能使用它。不過請注意,如果新的 VFS 是 SQLite 已知的唯一 VFS(如果 SQLite 是在沒有其一般預設 VFS 的情況下編譯,或者如果已使用 sqlite3_vfs_unregister() 移除了預先編譯的預設 VFS),則新的 VFS 會自動成為預設的 VFS,而不管 sqlite3_vfs_register() 的 makeDflt 引數為何。
標準建置包含預設的「unix」或「win32」VFS。但如果您使用 -DOS_OTHER=1 編譯時間選項,則 SQLite 會在沒有預設 VFS 的情況下建置。在這種情況下,應用程式必須在呼叫 sqlite3_open() 之前註冊至少一個 VFS。這是嵌入式應用程式應採用的方法。與修改 SQLite 原始碼以插入替代作業系統層(如在 SQLite 先前版本中所做)不同,請使用 -DOS_OTHER=1 選項編譯未修改的 SQLite 原始碼檔案(最好是合併),然後呼叫 sqlite3_vfs_register() 來定義與底層檔案系統的介面,然後才能建立任何資料庫連線。
sqlite3_vfs_unregister() API 用於從系統中移除現有的 VFS。
int sqlite3_vfs_unregister(sqlite3_vfs*);
sqlite3_vfs_find() API 用於按名稱尋找特定 VFS。其原型如下
sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName);
引數是所需 VFS 的符號名稱。如果引數是 NULL 指標,則會傳回預設 VFS。此函式傳回實作 VFS 的 sqlite3_vfs 物件指標。或者,如果找不到符合搜尋條件的物件,則會傳回 NULL 指標。
一旦 VFS 已註冊,就永遠不應修改。如果需要變更行為,應註冊新的 VFS。應用程式或許可以使用 sqlite3_vfs_find() 找到舊的 VFS,將舊的 VFS 複製到新的 sqlite3_vfs 物件中,對新的 VFS 進行所需的修改,取消註冊舊的 VFS,然後註冊新的 VFS 取代它。現有的資料庫連線會在取消註冊後繼續使用舊的 VFS,但新的資料庫連線會使用新的 VFS。
VFS 物件是下列結構的實例
typedef struct sqlite3_vfs sqlite3_vfs; struct sqlite3_vfs { int iVersion; /* Structure version number */ int szOsFile; /* Size of subclassed sqlite3_file */ int mxPathname; /* Maximum file pathname length */ sqlite3_vfs *pNext; /* Next registered VFS */ const char *zName; /* Name of this virtual file system */ void *pAppData; /* Pointer to application-specific data */ int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*, int flags, int *pOutFlags); int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir); int (*xAccess)(sqlite3_vfs*, const char *zName, int flags); int (*xGetTempName)(sqlite3_vfs*, char *zOut); int (*xFullPathname)(sqlite3_vfs*, const char *zName, char *zOut); void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename); void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg); void *(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol); void (*xDlClose)(sqlite3_vfs*, void*); int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut); int (*xSleep)(sqlite3_vfs*, int microseconds); int (*xCurrentTime)(sqlite3_vfs*, double*); /* New fields may be appended in figure versions. The iVersion ** value will increment whenever this happens. */ };
若要建立新的 VFS,應用程式會以適當的值填入此結構的實例,然後呼叫 sqlite3_vfs_register()。
對於 SQLite 版本 3.5.0,sqlite3_vfs 的 iVersion 欄位應該是 1。如果我們必須以某種方式修改 VFS 物件,此數字可能會在未來的 SQLite 版本中增加。我們希望這永遠不會發生,但已備妥此預留空間以防萬一。
szOsFile 欄位是定義開啟檔案的結構(sqlite3_file 物件)的位元組大小。此物件將在下方更詳細地描述。重點在於每個 VFS 實作都可以定義自己的 sqlite3_file 物件,其中包含 VFS 實作需要儲存關於開啟檔案的任何資訊。然而,SQLite 需要知道此物件有多大,才能預先配置足夠的空間來容納它。
mxPathname 欄位是此 VFS 可使用的檔案路徑名稱的最大長度。SQLite 有時必須預先配置此大小的緩衝區,因此應盡可能小。有些檔案系統允許使用很長的路徑名稱,但實際上路徑名稱很少會超過 100 個位元組左右。您不必在此處放置底層檔案系統可以處理的最長路徑名稱。您只需要放置您希望 SQLite 能夠處理的最長路徑名稱即可。在大部分情況下,幾百個是一個不錯的值。
pNext 欄位由 SQLite 內部使用。具體來說,SQLite 使用此欄位來形成已註冊 VFS 的連結清單。
zName 欄位是 VFS 的符號名稱。這是 sqlite3_vfs_find() 在尋找 VFS 時用來比較的名稱。
SQLite 核心不會使用 pAppData 指標。此指標可用於儲存 VFS 資訊可能想要攜帶的輔助資訊。
其餘的 sqlite3_vfs 物件欄位都儲存指向函式的指標,用於實作原始操作。我們稱這些為「方法」。第一個方法 xOpen,用於開啟基礎儲存媒體上的檔案。結果是一個 sqlite3_file 物件。還有其他方法,由 sqlite3_file 物件本身定義,用於讀取、寫入和關閉檔案。其他方法詳述如下。檔案名稱使用 UTF-8。SQLite 會保證傳遞給 xOpen() 的 zFilename 字串是 xFullPathname() 所產生的完整路徑名稱,而且這個字串會保持有效且不變,直到呼叫 xClose() 為止。因此,如果 sqlite3_file 需要記住檔案名稱,它可以儲存指向檔案名稱的指標。xOpen() 的 flags 引數是 sqlite3_open_v2() 的 flags 引數的複本。如果使用 sqlite3_open() 或 sqlite3_open16(),則 flags 是 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE。如果 xOpen() 開啟一個唯讀檔案,則它會設定 *pOutFlags 以包含 SQLITE_OPEN_READONLY。*pOutFlags 中的其他位元可能會被設定。SQLite 也會根據所開啟的物件,將下列其中一個旗標加入到 xOpen() 呼叫中
SQLITE_OPEN_TEMP_DB 資料庫和 SQLITE_OPEN_TRANSIENT_DB 資料庫之間的差異如下:SQLITE_OPEN_TEMP_DB 用於明確宣告且命名的 TEMP 資料表(使用 CREATE TEMP TABLE 語法),或用於暫時資料庫中的命名資料表,該資料庫是透過開啟檔案名稱為空字串的資料庫來建立。SQLITE_OPEN_TRANSIENT_DB 包含資料庫資料表,SQLite 會自動建立該資料表以評估子查詢或 ORDER BY 或 GROUP BY 子句。TEMP_DB 和 TRANSIENT_DB 資料庫都是私密的,而且會自動刪除。TEMP_DB 資料庫會持續資料庫連線的期間。TRANSIENT_DB 資料庫只會持續單一 SQL 陳述式的期間。
xDelete 方法用於刪除檔案。檔案名稱在第二個參數中給出。檔案名稱將會是 UTF-8。VFS 必須將檔案名稱轉換成底層作業系統預期的任何字元表示。如果 syncDir 參數為 true,則 xDelete 方法不應在已刪除檔案所包含目錄的目錄內容變更同步到磁碟以確保檔案不會在斷電後「重新出現」之前傳回。
xAccess 方法用於檢查檔案的存取權限。檔案名稱將會是 UTF-8 編碼。flags 參數將會是 SQLITE_ACCESS_EXISTS 以檢查檔案是否存在、SQLITE_ACCESS_READWRITE 以檢查檔案是否可讀寫,或 SQLITE_ACCESS_READ 以檢查檔案是否至少可讀。第二個參數所命名的「檔案」可能是目錄或資料夾名稱。
xGetTempName 方法計算 SQLite 可以使用的暫時檔案名稱。名稱應寫入第二個參數所提供的緩衝區。SQLite 將調整該緩衝區大小,以容納至少 mxPathname 位元組。產生的檔案名稱應為 UTF-8。為避免安全問題,產生的暫時檔案名稱應包含足夠的隨機性,以防止攻擊者事先猜測暫時檔案名稱。
xFullPathname 方法用於將相對路徑名稱轉換為完整路徑名稱。產生的完整路徑名稱寫入第三個參數所提供的緩衝區。SQLite 將調整輸出緩衝區大小,以容納至少 mxPathname 位元組。輸入和輸出名稱都應為 UTF-8。
xDlOpen、xDlError、xDlSym 和 xDlClose 方法都用於在執行時間存取共用函式庫。如果函式庫是用 SQLITE_OMIT_LOAD_EXTENSION 編譯,或者從未使用 sqlite3_enable_load_extension() 介面來啟用動態延伸模組載入,則可以省略這些方法(並將其指標設為零)。xDlOpen 方法會開啟共用函式庫或 DLL,並傳回指標至控制代碼。如果開啟失敗,則會傳回 NULL。如果開啟失敗,則可以使用 xDlError 方法來取得文字錯誤訊息。訊息會寫入第三個參數的 zErrMsg 緩衝區中,其長度至少為 nByte 位元組。xDlSym 會傳回指標至共用函式庫中的符號。符號的名稱由第二個參數給定。假設為 UTF-8 編碼。如果找不到符號,則會傳回 NULL 指標。xDlClose 常式會關閉共用函式庫。
xRandomness 方法會使用一次來初始化 SQLite 內部的偽亂數產生器 (PRNG)。只會使用預設 VFS 上的 xRandomness 方法。SQLite 永遠不會存取其他 VFS 上的 xRandomness 方法。xRandomness 常式會要求將 nByte 位元組的亂數寫入 zOut。常式會傳回取得的實際亂數位元組數。如此取得的亂數品質會決定由內建 SQLite 函式(例如 random() 和 randomblob())產生的亂數品質。SQLite 也會使用其 PRNG 來產生暫存檔名。在某些平台上(例如:Windows),SQLite 會假設暫存檔名是唯一的,而不會實際測試是否有衝突,因此即使從未使用 random() 和 randomblob() 函式,也必須要有高品質的亂數。
xSleep 方法用於暫停呼叫執行緒,時間至少為指定的微秒數。此方法用於實作 sqlite3_sleep() 和 sqlite3_busy_timeout() API。在 sqlite3_sleep() 的情況下,預設 VFS 的 xSleep 方法總是會被使用。如果底層系統沒有微秒解析度睡眠功能,則睡眠時間應向上取整。xSleep 傳回此向上取整的值。
xCurrentTime 方法會尋找目前的時間和日期,並將結果寫入第二個參數提供的指標中,為雙精度浮點值。時間和日期為協調世界時 (UTC),且為小數儒略日數。
開啟檔案的結果為 sqlite3_file 物件的執行個體。sqlite3_file 物件為抽象基底類別,定義如下
typedef struct sqlite3_file sqlite3_file; struct sqlite3_file { const struct sqlite3_io_methods *pMethods; };
每個 VFS 實作會透過在 sqlite3_file 結尾處新增額外欄位,來建立其子類別,以儲存 VFS 需要知道的開啟檔案資訊。儲存的資訊內容並不重要,只要結構的總大小不超過 sqlite3_vfs 物件中記錄的 szOsFile 值即可。
sqlite3_io_methods 物件為一個結構,其中包含用於讀取、寫入和處理檔案的方法指標。此物件定義如下
typedef struct sqlite3_io_methods sqlite3_io_methods; struct sqlite3_io_methods { int iVersion; int (*xClose)(sqlite3_file*); int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst); int (*xTruncate)(sqlite3_file*, sqlite3_int64 size); int (*xSync)(sqlite3_file*, int flags); int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize); int (*xLock)(sqlite3_file*, int); int (*xUnlock)(sqlite3_file*, int); int (*xCheckReservedLock)(sqlite3_file*); int (*xFileControl)(sqlite3_file*, int op, void *pArg); int (*xSectorSize)(sqlite3_file*); int (*xDeviceCharacteristics)(sqlite3_file*); /* Additional methods may be added in future releases */ };
sqlite3_io_methods 的 iVersion 欄位用於確保未來增強功能。對於 SQLite 版本 3.5,iVersion 值應始終為 1。
xClose 方法會關閉檔案。sqlite3_file 結構的空間由呼叫者解除配置。但是,如果 sqlite3_file 包含指向其他已配置記憶體或資源的指標,則應由 xClose 方法釋放這些配置。
xRead 方法從檔案的位元組偏移量 iOfst 開始讀取 iAmt 位元組。讀取的資料儲存在第二個參數的指標中。xRead 在成功時傳回 SQLITE_OK,如果無法讀取完整的位元組數,因為已到達檔案結尾,則傳回 SQLITE_IOERR_SHORT_READ,或對於任何其他錯誤傳回 SQLITE_IOERR_READ。
xWrite 方法將第二個參數的 iAmt 位元組資料寫入檔案,從 iOfst 位元組的偏移量開始。如果在寫入之前檔案大小小於 iOfst 位元組,則 xWrite 應確保在開始寫入之前,將檔案延伸為零,最多延伸到 iOfst 位元組。xWrite 會持續延伸檔案,視需要而定,以便在 xWrite 呼叫結束時,檔案大小至少為 iAmt+iOfst 位元組。xWrite 方法在成功時傳回 SQLITE_OK。如果無法完成寫入,因為基礎儲存媒體已滿,則傳回 SQLITE_FULL。對於任何其他錯誤,應傳回 SQLITE_IOERR_WRITE。
xTruncate 方法將檔案截斷為 nByte 位元組長度。如果檔案已經是 nByte 位元組或更短,則此方法為無操作。xTruncate 方法在成功時傳回 SQLITE_OK,如果發生任何問題,則傳回 SQLITE_IOERR_TRUNCATE。
xSync 方法用於強制將先前寫入的資料從作業系統快取寫入非揮發性記憶體。第二個參數通常是 SQLITE_SYNC_NORMAL。如果第二個參數是 SQLITE_SYNC_FULL,則 xSync 方法應確保資料也已透過磁碟控制器快取清除。SQLITE_SYNC_FULL 參數等同於 Mac OS X 上的 F_FULLSYNC ioctl()。xSync 方法在成功時傳回 SQLITE_OK,如果發生任何問題,則傳回 SQLITE_IOERR_FSYNC。
xFileSize() 方法判斷檔案目前的位元組大小,並將該值寫入 *pSize。在成功時傳回 SQLITE_OK,如果發生任何問題,則傳回 SQLITE_IOERR_FSTAT。
xLock 和 xUnlock 方法用於設定和清除檔案鎖定。SQLite 支援五個層級的檔案鎖定,依序為
基礎實作可以支援這些鎖定層級的某些子集,只要它符合本段的其他需求。鎖定層級指定為 xLock 和 xUnlock 的第二個參數。xLock 方法會將鎖定層級提高到指定的鎖定層級或更高。xUnlock 方法會將鎖定層級降低到不低於指定層級。 SQLITE_LOCK_NONE 表示檔案已解鎖。 SQLITE_LOCK_SHARED 允許讀取檔案。多個資料庫連線可以同時持有 SQLITE_LOCK_SHARED。 SQLITE_LOCK_RESERVED 類似於 SQLITE_LOCK_SHARED,在於它允許讀取檔案。但是,在任何時間點,只有一個連線可以持有保留鎖定。 SQLITE_LOCK_PENDING 也允許讀取檔案。其他連線也可以繼續讀取檔案,但是不允許其他連線將鎖定從「無」升級為「共用」。 SQLITE_LOCK_EXCLUSIVE 允許寫入檔案。只有一個連線可以持有獨佔鎖定,而且當一個連線持有獨佔鎖定時,沒有其他連線可以持有任何鎖定(除了「無」)。xLock 在成功時傳回 SQLITE_OK,在無法取得鎖定時傳回 SQLITE_BUSY,或在其他事項出錯時傳回 SQLITE_IOERR_RDLOCK。xUnlock 方法在成功時傳回 SQLITE_OK,在有問題時傳回 SQLITE_IOERR_UNLOCK。xCheckReservedLock() 方法檢查其他連線或其他處理程序目前是否對檔案持有保留、待處理或獨佔鎖定。它傳回 true 或 false。
xFileControl() 方法是一個通用介面,允許自訂 VFS 實作使用(新的和實驗性的)sqlite3_file_control() 介面直接控制開啟的檔案。第二個「op」引數是一個整數操作碼。第三個引數是一個通用指標,用於指向一個結構,其中可能包含引數或用於寫入回傳值的空間。xFileControl() 的潛在用途可能是啟用具有逾時功能的封鎖鎖定、變更鎖定策略(例如使用點檔案鎖定)、查詢鎖定的狀態或中斷過期的鎖定。SQLite 核心保留小於 100 的操作碼供自己使用。小於 100 的操作碼清單可用。定義自訂 xFileControl 方法的應用程式應使用大於 100 的操作碼,以避免衝突。
xSectorSize 回傳基礎非揮發性媒體的「區塊大小」。「區塊」定義為可以在不影響相鄰儲存空間的情況下寫入的最小儲存單位。在磁碟機上,「區塊大小」直到最近都是 512 位元組,儘管有人推動將此值增加到 4KiB。SQLite 需要知道區塊大小,以便一次寫入一個完整的區塊,並在寫入過程中發生斷電時避免損毀相鄰的儲存空間。
xDeviceCharacteristics 方法回傳一個整數位元向量,定義基礎儲存媒體可能具有的任何特殊屬性,SQLite 可以使用這些屬性來提升效能。允許的回傳值是下列值的按位元 OR
前述段落包含大量資訊。為了簡化建立 SQLite 新 VFS 的任務,我們提供下列實作檢查清單
在你的應用程式中,在你開啟任何資料庫連線之前,呼叫在上面最後一步驟中實作的程序,作為你的初始化程序的一部分。
從版本 3.5 開始,SQLite 使用 sqlite3_malloc()、sqlite3_free() 和 sqlite3_realloc() 常式取得它需要的堆疊記憶體。這些常式在 SQLite 的先前版本中已經存在,但 SQLite 以前會略過這些常式,並使用它自己的記憶體配置器。這一切在版本 3.5.0 中都改變了。
SQLite 原始碼樹實際上包含記憶體配置器的多個版本。「mem1.c」原始檔中找到的預設高速版本用於大多數建置。但如果啟用了 SQLITE_MEMDEBUG 旗標,則會改用「mem2.c」原始檔中的一個獨立記憶體配置器。mem2.c 配置器實作許多掛鉤,用於執行錯誤檢查,並模擬記憶體配置失敗以進行測試。這兩個配置器都使用標準 C 函式庫中的 malloc()/free() 實作。
應用程式不需要使用這些標準記憶體配置器。如果 SQLite 是使用 SQLITE_OMIT_MEMORY_ALLOCATION 編譯的,則不會提供 sqlite3_malloc()、sqlite3_realloc() 和 sqlite3_free() 函式的實作。相反地,連結到 SQLite 的應用程式必須提供這些函式的實作。應用程式提供的記憶體配置器不需要使用標準 C 函式庫中的 malloc()/free() 實作。例如,嵌入式應用程式可能會提供使用固定記憶體池的替代記憶體配置器,該記憶體池專供 SQLite 使用。
實作自己的記憶體配置器的應用程式必須提供一般三個配置函式的實作:sqlite3_malloc()、sqlite3_realloc() 和 sqlite3_free()。它們還必須實作第四個函式
int sqlite3_memory_alarm( void(*xCallback)(void *pArg, sqlite3_int64 used, int N), void *pArg, sqlite3_int64 iThreshold );
sqlite3_memory_alarm 常式用於在記憶體配置事件中註冊回呼。此常式註冊或清除回呼,當已配置的記憶體量超過 iThreshold 時觸發。一次只能註冊一個回呼。sqlite3_memory_alarm() 的每次呼叫都會覆寫前一個回呼。透過將 xCallback 設為 NULL 指標來停用回呼。
回呼的參數為 pArg 值、目前使用的記憶體量,以及觸發回呼的配置大小。回呼可能會呼叫 sqlite3_free() 來釋放記憶體空間。回呼可能會呼叫 sqlite3_malloc() 或 sqlite3_realloc(),但如果呼叫,遞迴呼叫不會呼叫其他回呼。
sqlite3_soft_heap_limit() 介面透過在軟性堆積限制中註冊記憶體警示,並在警示回呼中呼叫 sqlite3_release_memory() 來運作。應用程式不應嘗試使用 sqlite3_memory_alarm() 介面,因為這樣會干擾 sqlite3_soft_heap_limit() 模組。此介面僅公開,以便在 SQLite 核心使用 SQLITE_OMIT_MEMORY_ALLOCATION 編譯時,應用程式可以提供自己的替代實作。
SQLite 中內建的記憶體配置器也提供下列額外的介面
sqlite3_int64 sqlite3_memory_used(void); sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
這些介面可供應用程式監控 SQLite 使用的記憶體量。sqlite3_memory_used() 常式回傳目前使用中的記憶體位元組數,而 sqlite3_memory_highwater() 則回傳瞬間記憶體使用量最大值。這兩個常式都不包括與記憶體配置器相關的開銷。這些常式供應用程式使用。SQLite 本身絕不會自行呼叫它們。因此,如果應用程式提供自己的記憶體配置子系統,則可視需要略過這些介面。
SQLite 一直都是執行緒安全的,意即可以在不同的執行緒中同時使用不同的 SQLite 資料庫連線。限制在於無法在兩個不同的執行緒中同時使用同一個資料庫連線。SQLite 版本 3.5.0 放寬了此限制。
為了允許多個執行緒同時使用同一個資料庫連線,SQLite 必須廣泛使用互斥鎖。因此,新增了一個新的互斥鎖子系統。互斥鎖子系統有下列介面
sqlite3_mutex *sqlite3_mutex_alloc(int); void sqlite3_mutex_free(sqlite3_mutex*); void sqlite3_mutex_enter(sqlite3_mutex*); int sqlite3_mutex_try(sqlite3_mutex*); void sqlite3_mutex_leave(sqlite3_mutex*);
儘管這些常式是供 SQLite 核心使用,但應用程式程式碼也可以自由使用這些常式(如果需要)。互斥鎖是一個 sqlite3_mutex 物件。sqlite3_mutex_alloc() 常式會配置一個新的互斥鎖物件並傳回指向該物件的指標。sqlite3_mutex_alloc() 的引數應該是 SQLITE_MUTEX_FAST 或 SQLITE_MUTEX_RECURSIVE,分別代表非遞迴和遞迴互斥鎖。如果底層系統不提供非遞迴互斥鎖,則可以在這種情況下替換為遞迴互斥鎖。sqlite3_mutex_alloc() 的引數也可以是一個常數,用來指定幾個靜態互斥鎖之一
應使用 sqlite3_mutex_free() 常式來取消配置非靜態互斥鎖。如果將靜態互斥鎖傳遞給這個常式,則行為未定義。
sqlite3_mutex_enter() 嘗試進入互斥鎖,如果其他執行緒已經在那裡,則會封鎖。 sqlite3_mutex_try() 嘗試進入,如果成功則傳回 SQLITE_OK,如果其他執行緒已經在那裡,則傳回 SQLITE_BUSY。 sqlite3_mutex_leave() 離開互斥鎖。互斥鎖會一直保持,直到離開次數與進入次數相符。如果對執行緒目前未持有的互斥鎖呼叫 sqlite3_mutex_leave(),則行為未定義。如果對已取消配置的互斥鎖呼叫任何常式,則行為未定義。
SQLite 原始碼提供這些 API 的多種實作,適用於不同的環境。如果 SQLite 是使用 SQLITE_THREADSAFE=0 標記編譯的,則會提供一個無操作的互斥鎖實作,速度很快,但不會進行真正的互斥。此實作適用於單執行緒應用程式或只在單一執行緒中使用 SQLite 的應用程式。其他真正的互斥鎖實作是根據底層作業系統提供的。
嵌入式應用程式可能希望提供自己的互斥鎖實作。如果 SQLite 是使用 -DSQLITE_MUTEX_APPDEF=1 編譯時間標記編譯的,則 SQLite 核心不會提供互斥鎖子系統,而連結到 SQLite 的應用程式必須提供一個與上述介面相符的互斥鎖子系統。
SQLite 的 3.5.0 版變更了幾個 API 的行為,在技術上不相容。然而,這些 API 很少使用,即使使用時也很難想像變更可能會破壞什麼。變更實際上讓這些介面更有用、更強大。
在 3.5.0 版本之前,sqlite3_enable_shared_cache() API 會啟用和停用單一執行緒內所有連線的共用快取功能,也就是呼叫 sqlite3_enable_shared_cache() 常式的執行緒。使用共用快取的資料庫連線會受限於在開啟的執行緒中執行。從 3.5.0 版本開始,sqlite3_enable_shared_cache() 適用於處理程序內所有執行緒的所有資料庫連線。現在,在不同執行緒中執行的資料庫連線可以共用快取。而使用共用快取的資料庫連線可以從一個執行緒移轉到另一個執行緒。
在 3.5.0 版本之前,sqlite3_soft_heap_limit() 會設定單一執行緒內所有資料庫連線的堆疊記憶體使用量上限。每個執行緒可以有自己的堆疊限制。從 3.5.0 版本開始,整個處理程序只有一個堆疊限制。這似乎更具限制性(一個限制而非多個限制),但實際上這是大多數使用者想要的。
在 3.5.0 版本之前,sqlite3_release_memory() 函式會嘗試從與 sqlite3_release_memory() 呼叫相同的執行緒中的所有資料庫連線回收記憶體。從 3.5.0 版本開始,sqlite3_release_memory() 函式會嘗試從所有執行緒中的所有資料庫連線回收記憶體。
從 SQLite 3.4.2 版本過渡到 3.5.0 版本是一個重大的改變。SQLite 核心中的每個原始碼檔案都必須修改,有些甚至需要大幅修改。而且這個改變在 C 介面中引入了些許不相容性。但我們認為從 3.4.2 過渡到 3.5.0 的好處遠遠大於移植的痛苦。新的 VFS 層現在定義明確且穩定,而且應該可以簡化未來的自訂。VFS 層,以及可分離的記憶體配置器和互斥子系統允許在嵌入式專案中使用標準 SQLite 原始碼合併,而不需要變更,大幅簡化組態管理。而且產生的系統更能容忍高度執行緒的設計。
此頁面最後修改於 2022-01-08 05:02:57 UTC