文件數據庫sqlite3 C++ 線程安全和併發

轉載: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

轉載:https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286361&idx=1&sn=78bbcda7f41a14291ad71289e4821f71&scene=21(多線程併發優化)

1、SQLite 與線程

SQLite 是線程安全的。

線程模型

SQLite 支持以下三種線程模型

  • 單線程模型 這種模型下,全部互斥鎖都被禁用,同一時間只能由一個線程訪問。
  • 多線程模型 這種模型下,一個鏈接在同一時間內只有一個線程使用就是安全的。
  • 串行模型 開啓全部鎖,能夠隨意訪問。

設置線程模型

SQLite 能夠經過如下三種方式進行線程模型的設置,在實際應用中選擇任一一項均可以。

  • 編譯期設定 經過 SQLITE_THREADSAFE 這個參數進行編譯器的設定來選擇線程模型
  • 初始化設定 經過調用 sqlite3_config() 能夠在 SQLite 初始化時進行設定
  • 運行時設定 經過調用 sqlite3_open_v2() 接口指定數據庫鏈接的數據庫模型

SQLite 併發和事務

事務

事務是 SQLite 的核心概念。對數據庫的操做 (絕大部分) 會被打包成一個事務進行提交,須要注意的是,這裏的打包成事務是自動開啓的。舉例而言,若是簡單在一個 for 循環語句裏向數據庫中插入 10 條數據,意味着將自動生成 10 個事務。但須要注意的是事務是很是耗時的,通常而言, SQLite 每秒可以輕鬆支持 50000 條的數據插入,可是每秒僅可以支持幾十個事務。通常而言,事務速度受限於磁盤速度。因此在批量插入時須要考慮禁用自動提交,將其用 BEGIN ... COMMIT 打包成一個事務。

回滾模式和 WAL

爲了保證寫入正確,SQLite 在使用事務進行數據庫改寫時將拷貝當前數據庫文件的備份,即 rollback journal,當事務失敗或者發生意外須要回滾時則將備份文件內容還原到數據庫中,並同時刪除該日誌。這是默認的 DELETE 模式。

然後 SQLite 也引入了 WAL 模式,即 Write-Ahead Log。在這種模式下,全部的修改會寫入一個單獨的 WAL 文件內。這種模式下,寫操做甚至能夠不去操做數據庫,這使得全部的讀操做能夠在 "寫的同時" 直接對數據庫文件進行操做,獲得更好的併發性能。

鎖和併發

SQLite 經過五種鎖狀態來完成事務。

  • UNLOCKED ,無鎖狀態。數據庫文件沒有被加鎖。
  • SHARED 共享狀態。數據庫文件被加了共享鎖。能夠多線程執行讀操做,但不能進行寫操做。
  • RESERVED 保留狀態。數據庫文件被加保留鎖。表示數據庫將要進行寫操做。
  • PENDING 未決狀態。表示即將寫入數據庫,正在等待其餘讀線程釋放 SHARED 鎖。一旦某個線程持有 PENDING 鎖,其餘線程就不能獲取 SHARED 鎖。這樣一來,只要等全部讀線程完成,釋放 SHARED 鎖後,它就能夠進入 EXCLUSIVE 狀態了。
  • EXCLUSIVE 獨佔鎖。表示它能夠寫入數據庫了。進入這個狀態後,其餘任何線程都不能訪問數據庫文件。所以爲了併發性,它的持有時間越短越好。

一個線程只有擁有低級別鎖時纔可以得到更高一級的鎖

/*

** 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.

*/

總結

綜上所述,要保證數據庫使用的安全,通常能夠採用以下幾種模式

  • SQLite 採用單線程模型,用專門的線程/隊列(同時只能有一個任務執行訪問) 進行訪問
  • SQLite 採用多線程模型,每一個線程都使用各自的數據庫鏈接 (即 sqlite3 *)
  • SQLite 採用串行模型,全部線程都公用同一個數據庫鏈接。

由於寫操做的併發性並很差,當多線程進行訪問時實際上仍舊須要互相等待,而讀操做所須要的 SHARED 鎖是能夠共享的,因此爲了保證最高的併發性,推薦

  • 使用多線程模式
  • 使用 WAL 模式
  • 單線程寫,多線程讀 (各線程都持有本身對應的數據庫鏈接)
  • 避免長時間事務
  • 緩存 sqlite3_prepare 編譯結果
  • 多語句經過 BEGIN 和 COMMIT 作顯示事務,減小屢次的自動事務消耗

2、WAL 機制的原理是:

     修改並不直接寫入到數據庫文件中,而是寫入到另一個稱爲 WAL 的文件中;若是事務失敗,WAL 中的記錄會被忽略,撤銷修改;若是事務成功,它將在隨後的某個時間被寫回到數據庫文件中,提交修改。 同步 WAL 文件和數據庫文件的行爲被稱爲 checkpoint(檢查點),它由 SQLite 自動執行,默認是在 WAL 文件積累到 1000 頁修改的時候;固然,在適當的時候,也能夠手動執行 checkpoint,SQLite 提供了相關的接口。執行 checkpoint 以後,WAL 文件會被清空。 在讀的時候,SQLite 將在 WAL 文件中搜索,找到最後一個寫入點,記住它,並忽略在此以後的寫入點(這保證了讀寫和讀讀能夠並行執行);隨後,它肯定所要讀的數據所在頁是否在 WAL 文件中,若是在,則讀 WAL 文件中的數據,若是不在,則直接讀數據庫文件中的數據。 在寫的時候,SQLite 將之寫入到 WAL 文件中便可,可是必須保證獨佔寫入,所以寫寫之間不能並行執行。

2.1 wal工做原理

在引入WAL機制以前,SQLite使用rollbackjournal機制實現原子事務。

rollback journal機制的原理是:在修改數據庫文件中的數據以前,先將修改所在分頁中的數據備份在另一個地方,而後纔將修改寫入到數據庫文件中;若是事務失敗,則將備份數據拷貝回來,撤銷修改;若是事務成功,則刪除備份數據,提交修改。

WAL機制的原理是:修改並不直接寫入到數據庫文件中,而是寫入到另一個稱爲WAL的文件中;若是事務失敗,WAL中的記錄會被忽略,撤銷修改;若是事務成功,它將在隨後的某個時間被寫回到數據庫文件中,提交修改。

2.2 wal優勢:

1.      讀和寫能夠徹底地併發執行,不會互相阻塞(可是寫之間仍然不能併發)。

2.      WAL在大多數狀況下,擁有更好的性能(由於無需每次寫入時都要寫兩個文件)。

3.      磁盤I/O行爲更容易被預測。

2.3 wal缺點:

1.      訪問數據庫的全部程序必須在同一主機上,且支持共享內存技術。

2.      每一個數據庫如今對應3個文件:<yourdb>.db,<yourdb>-wal,<yourdb>-shm。

3.      當寫入數據達到GB級的時候,數據庫性能將降低。

4.      3.7.0以前的SQLite沒法識別啓用了WAL機制的數據庫文件。

2.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()函數。

3、開啓WAL機制

 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打開同一個數據庫文件,每個線程維護一個數據庫鏈接對象,這樣發生寫競爭衝突的時候,能夠經過回調函數重試,解決併發寫
 
線程安全:是指二個或三個線程能夠同時調用獨立的不一樣的sqlite3_open() 返回的"sqlite3"結構。而不是在多線程中同時使用同一個 sqlite3 結構指針。 一個sqlite3結構只能在調用 sqlite3_open建立它的那個進程中使用。你不能在一個線程中打開一個數據庫而後把指針傳遞給另外一個線程使用。這是由於大多數多線程系統的限制
相關文章
相關標籤/搜索