Sqlite在多線程下的使用方法及注意的事項
一Sqlite的三種模式
1. 單線程,這種模式下,沒有進行互斥,多線程使用不安全
2. 多線程,這種模式下,在多線程中使用單個數據庫連接是不安全的,否則就是安全的。也就是不能再多個線程中共享數據庫連接
3. 串行,這種模式下,sqlite是線程安全的。可以在多個線程中不加互斥的使用同一個數據庫連接
數據庫使用經驗:線程模式可以在編譯時(通過源碼編譯sqlite庫時)、啓動時(使用sqlite的應用程序初始化時)或者運行時(創建數據庫連接時)來指定。一般而言,運行時指定的模式將覆蓋啓動時的指定模式,啓動時指定的模式將覆蓋編譯時指定的模式。但是,單線程模式一旦被指定,將無法被覆蓋。所以我們在創建數據庫的時候,就一定要考慮好數據庫使用的模式。
二Is Sqlite3 thread-safe?
答案當然是肯定的。不過要確保線程安全需要(注意事項)
1. 確保編譯時使用:DTHREADSAFE = 1
2. 不要在多個線程中共享數據庫鏈接
3. 在一些操作系統中,一個線程中只能創建並使用一個數據庫連接(建議使用者在所有操作系統的使用上都堅持此項原則)
4. Sqlite有一小部分功能(PRAGMAtemp_store_directory等等)是線程不安全的,多線程中應當避免使用
三讀寫不阻塞的Sqlite-Wal模式
3.1 wal工作原理
在引入WAL機制之前,SQLite使用rollbackjournal機制實現原子事務。
rollback journal機制的原理是:在修改數據庫文件中的數據之前,先將修改所在分頁中的數據備份在另外一個地方,然後纔將修改寫入到數據庫文件中;如果事務失敗,則將備份數據拷貝回來,撤銷修改;如果事務成功,則刪除備份數據,提交修改。
WAL機制的原理是:修改並不直接寫入到數據庫文件中,而是寫入到另外一個稱爲WAL的文件中;如果事務失敗,WAL中的記錄會被忽略,撤銷修改;如果事務成功,它將在隨後的某個時間被寫回到數據庫文件中,提交修改。
3.2 wal優點:
1. 讀和寫可以完全地併發執行,不會互相阻塞(但是寫之間仍然不能併發)。
2. WAL在大多數情況下,擁有更好的性能(因爲無需每次寫入時都要寫兩個文件)。
3. 磁盤I/O行爲更容易被預測。
3.3 wal缺點:
1. 訪問數據庫的所有程序必須在同一主機上,且支持共享內存技術。
2. 每個數據庫現在對應3個文件:<yourdb>.db,<yourdb>-wal,<yourdb>-shm。
3. 當寫入數據達到GB級的時候,數據庫性能將下降。
4. 3.7.0之前的SQLite無法識別啓用了WAL機制的數據庫文件。
3.4 wal如何記錄數據--checkpoint
使用WAL模式時,改寫操作是附加(append)到WAL文件,而不改動數據庫文件,因此數據庫文件可以被同時讀取。當執行checkpoint操作時,WAL文件的內容會被寫回數據庫文件。當WAL文件達到SQLITE_DEFAULT_WAL_AUTOCHECKPOINT(默認值是1000)頁(默認大小是1KB)時,會自動使用當前COMMIT的線程來執行checkpoint操作。也可以關閉自動checkpoint,改爲手動定期checkpoint。
爲了避免讀取的數據不一致,查詢時也需要讀取WAL文件,並記錄一個結尾標記(end mark)。這樣的代價就是讀取會變得稍慢,但是寫入會變快很多。要提高查詢性能的話,可以減小WAL文件的大小,但寫入性能也會降低。 需要注意的是,低版本的SQLite不能讀取高版本的SQLite生成的WAL文件,但是數據庫文件是通用的。這種情況在用戶進行iOS降級時可能會出現,可以把模式改成delete,再改回WAL來修復。
要對一個數據庫連接啓用WAL模式,需要執行“PRAGMA journal_mode=WAL;”這條命令,它的默認值是“journal_mode=DELETE”。執行後會返回新的journal_mode字符串值,即成功時爲"wal",失敗時爲之前的模式(例如"delete")。一旦啓用WAL模式後,數據庫會保持這個模式,這樣下次打開數據庫時仍然是 WAL模式。 要停止自動checkpoint,可以使用wal_autocheckpoint指令或sqlite3_wal_checkpoint()函數。手動執行 checkpoint可以使用wal_checkpoint指令或sqlite3_wal_checkpoint()函數。
四.Wal在c++代碼中的應用
int DataSource::InitDataBaseToWal(std::string sPath, bool isWal)
{
char* zErrMsg;
sqlite3* db = NULL;
int rc = sqlite3_open_v2(sPath.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX, NULL);
if (rc != SQLITE_OK)
{
Logger::LogD("DataSource::sqlite [%s] or [%s] open failed", sPath.c_str(), sqlite3_errmsg(db));
Logger::LogO("DataSource::sqlite [%s] or [%s] open failed", sPath.c_str(), sqlite3_errmsg(db));
sqlite3_close(db);
return -1;
}
if(isWal == true)
{
rc = sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, 0, &zErrMsg);
if (rc != SQLITE_OK)
{
sqlite3_free(zErrMsg);
sqlite3_close(db);
return -1;
}
rc = sqlite3_exec(db, "PRAGMA wal_autocheckpoint=100;", NULL, 0, &zErrMsg);
if (rc != SQLITE_OK)
{
sqlite3_free(zErrMsg);
sqlite3_close(db);
return -1;
}
}
else
{
rc = sqlite3_exec(db, "PRAGMA journal_mode=DELETE;", NULL, 0, &zErrMsg);
if (rc != SQLITE_OK)
{
sqlite3_free(zErrMsg);
sqlite3_close(db);
return -1;
}
}
return true;
}
說明:當isWal爲true時,啓動wal模式,isWal爲false時,關閉wal模式。當你看到這裏的時候,對sqlite的wal模式一定有了一定的瞭解,爲了讓你更加相信wal模式的利好,作者告訴你一個祕密,就是作者同時開啓5個線程(3讀2寫)進行了測試,未見異常。
調整checkpoint默認頁值,性能也基本能滿足需求。