轉載:http://www.javashuo.com/article/p-fecetapm-hy.html(線程安全和併發)html
轉載:https://juejin.im/post/5b7d8522e51d4538e5679f5e(WAL模式介紹)sql
轉載:https://blog.csdn.net/vannachen/article/details/8277344(多線程/WAL/鎖)數據庫
轉載:https://blog.csdn.net/wql2rainbow/article/details/73650056(怎麼開啓wal機制)緩存
轉載:https://www.cnblogs.com/cchust/category/802864.html(sqlite 詳細教程)安全
轉載:http://www.cnblogs.com/stephen-liu74/archive/2012/01/19/2326309.html(sqlite備份機制)多線程
轉載:https://blog.csdn.net/northcan/article/details/7231115(打開數據庫例子)併發
轉載:https://blog.51cto.com/linzimo777/1544202(相同的項目場景,解決方法)app
轉載:https://blog.csdn.net/wsmrcool/article/details/8287904(多線程多個數據庫鏈接)函數
轉載:https://www.jianshu.com/p/6a1ebd08f003(分庫)post
1、SQLite 與線程
SQLite 是線程安全的。
線程模型
SQLite 支持以下三種線程模型
設置線程模型
SQLite 能夠經過如下三種方式進行線程模型的設置,在實際應用中選擇任一一項均可以。
SQLite 併發和事務
事務
事務是 SQLite 的核心概念。對數據庫的操做 (絕大部分) 會被打包成一個事務進行提交,須要注意的是,這裏的打包成事務是自動開啓的。舉例而言,若是簡單在一個 for 循環語句裏向數據庫中插入 10 條數據,意味着將自動生成 10 個事務。但須要注意的是事務是很是耗時的,通常而言, SQLite 每秒可以輕鬆支持 50000 條的數據插入,可是每秒僅可以支持幾十個事務。通常而言,事務速度受限於磁盤速度。因此在批量插入時須要考慮禁用自動提交,將其用 BEGIN ... COMMIT 打包成一個事務。
回滾模式和 WAL
爲了保證寫入正確,SQLite 在使用事務進行數據庫改寫時將拷貝當前數據庫文件的備份,即 rollback journal,當事務失敗或者發生意外須要回滾時則將備份文件內容還原到數據庫中,並同時刪除該日誌。這是默認的 DELETE 模式。
然後 SQLite 也引入了 WAL 模式,即 Write-Ahead Log。在這種模式下,全部的修改會寫入一個單獨的 WAL 文件內。這種模式下,寫操做甚至能夠不去操做數據庫,這使得全部的讀操做能夠在 "寫的同時" 直接對數據庫文件進行操做,獲得更好的併發性能。
鎖和併發
SQLite 經過五種鎖狀態來完成事務。
一個線程只有擁有低級別鎖時纔可以得到更高一級的鎖
/*
** Lock the file with the lock specified by parameter eFileLock - one
** of the following:
**
** (1) SHARED_LOCK
** (2) RESERVED_LOCK
** (3) PENDING_LOCK
** (4) EXCLUSIVE_LOCK
**
** Sometimes when requesting one lock state, additional lock states
** are inserted in between. The locking might fail on one of the later
** transitions leaving the lock state different from what it started but
** still short of its goal. The following chart shows the allowed
** transitions and the inserted intermediate states:
**
** UNLOCKED -> SHARED
** SHARED -> RESERVED
** SHARED -> (PENDING) -> EXCLUSIVE
** RESERVED -> (PENDING) -> EXCLUSIVE
** PENDING -> EXCLUSIVE
**
** This routine will only increase a lock. Use the sqlite3OsUnlock()
** routine to lower a locking level.
*/
總結
綜上所述,要保證數據庫使用的安全,通常能夠採用以下幾種模式
由於寫操做的併發性並很差,當多線程進行訪問時實際上仍舊須要互相等待,而讀操做所須要的 SHARED 鎖是能夠共享的,因此爲了保證最高的併發性,推薦
在引入WAL機制以前,SQLite使用rollbackjournal機制實現原子事務。
rollback journal機制的原理是:在修改數據庫文件中的數據以前,先將修改所在分頁中的數據備份在另一個地方,而後纔將修改寫入到數據庫文件中;若是事務失敗,則將備份數據拷貝回來,撤銷修改;若是事務成功,則刪除備份數據,提交修改。
WAL機制的原理是:修改並不直接寫入到數據庫文件中,而是寫入到另一個稱爲WAL的文件中;若是事務失敗,WAL中的記錄會被忽略,撤銷修改;若是事務成功,它將在隨後的某個時間被寫回到數據庫文件中,提交修改。
1. 讀和寫能夠徹底地併發執行,不會互相阻塞(可是寫之間仍然不能併發)。
2. WAL在大多數狀況下,擁有更好的性能(由於無需每次寫入時都要寫兩個文件)。
3. 磁盤I/O行爲更容易被預測。
1. 訪問數據庫的全部程序必須在同一主機上,且支持共享內存技術。
2. 每一個數據庫如今對應3個文件:<yourdb>.db,<yourdb>-wal,<yourdb>-shm。
3. 當寫入數據達到GB級的時候,數據庫性能將降低。
4. 3.7.0以前的SQLite沒法識別啓用了WAL機制的數據庫文件。
使用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()函數。
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; }
4、多線程併發寫操做的安全性
sqlite實際支持的是多線程同時讀但只支持同一時刻一個線程寫,即所謂的多讀單寫,sqlite 支持 single-thread/multi-thread/serialized 三種不一樣的線程安全模式。能夠在編譯sqlite組件時進行配置,或者能夠經過 sqlite3_threadsafe()/sqlite3_config() 在程序運行時進行查看並配置線程安全模式。通過實際寫 demo 測試,進行 multi-thread 或 serialized 配置之後,多線程併發讀的場景下,沒有問題。可是多線程併發寫時依舊會拋錯 database is locked。事實證實Sqlite不支持併發執行寫入操做,即便是不一樣的表,只支持庫級鎖,並且這個Sqlite自己沒有實現,必須本身實現這個庫級鎖,經過查閱官網資料,發現sqlite提供兩個 busy handle 函數sqlite3_busy_timeout()/sqlite3_busy_handle() 在併發訪問失敗時,會調用註冊的 busy handle 函數,在註冊的自定義的 busy handle 函數中能夠進行處理(如重試n次等), 這種處理方式必須創建在多線程多個數據庫鏈接,多個數據庫鏈接能夠理解成,用sqlite3_open或者sqlite3_open_v2打開同一個數據庫文件,每個線程維護一個數據庫鏈接對象,這樣發生寫競爭衝突的時候,能夠經過回調函數重試,解決併發寫。