SQLite3性能優化1-多線程插入或者查詢操做方面

先看看總結的結論:html

A.  因而可知,要想保證線程安全的話,能夠有這4種方式:ios

  1. SQLite使用單線程模式,用一個專門的線程訪問數據庫。
  2. SQLite使用單線程模式,用一個線程隊列來訪問數據庫,隊列一次只容許一個線程執行,隊列裏的線程共用一個數據庫鏈接。
  3. SQLite使用多線程模式,每一個線程建立本身的數據庫鏈接。
  4. SQLite使用串行模式,全部線程共用全局的數據庫鏈接。

B.  關於數據庫鏈接和數據庫事務,以及線程之間的關係對應sql

    事務是和數據庫鏈接相關的,與線程是無關的。一個數據庫鏈接同時只能執行一個事務操做。數據庫

每一個數據庫鏈接(使用pager來)維護本身的事務,且同時只能有一個事務(可是能夠用SAVEPOINT來實現內嵌事務)。也就是說,事務與線程無關,一個線程裏能夠同時用多個數據庫鏈接來完成多個事務,而多個線程也能夠同時(非併發)使用一個數據庫鏈接來共同完成一個事務。安全

常常有人抱怨SQLite的插入太慢實際上它能夠作到每秒插入幾萬次,可是每秒只能提交幾十次事務。所以在插入大批數據時,能夠經過禁用自動提交來提速。數據庫只有在事務中才能被更改。全部更改數據庫的命令(除SELECT之外的全部SQL命令)都會自動開啓一個新事務,而且當最後一個查詢完成時自動提交。多線程

 

1、 是否支持多線程?併發

 

SQLite官網上的「Is SQLite threadsafe?」這個問答。 簡單來講,從3.3.1版本開始,它就是線程安全的了。而iOS的SQLite版本沒有低於這個版本的,固然,你也能夠本身編譯最新版本。iphone


不過這個線程安全仍然是有限制的,在這篇《Is SQLite thread-safe?》裏有詳細的解釋。
另外一篇重要的文檔就是《SQLite And Multiple Threads》。它指出SQLite支持3種線程模式:函數

  1. 單線程:禁用全部的mutex鎖,併發使用時會出錯。當SQLite編譯時加了SQLITE_THREADSAFE=0參數,或者在初始化SQLite前調用sqlite3_config(SQLITE_CONFIG_SINGLETHREAD)時啓用。
  2. 多線程:只要一個數據庫鏈接不被多個線程同時使用就是安全的。源碼中是啓用bCoreMutex,禁用bFullMutex。實際上就是禁用數據庫鏈接和prepared statement(準備好的語句)上的鎖,所以不能在多個線程中併發使用同一個數據庫鏈接或prepared statement。當SQLite編譯時加了SQLITE_THREADSAFE=2參數時默認啓用。若SQLITE_THREADSAFE不爲0,能夠在初始化SQLite前,調用sqlite3_config(SQLITE_CONFIG_MULTITHREAD)啓用;或者在建立數據庫鏈接時,設置SQLITE_OPEN_NOMUTEX flag。
  3. 串行:啓用全部的鎖,包括bCoreMutex和bFullMutex。由於數據庫鏈接和prepared statement都已加鎖,因此多線程使用這些對象時無法併發,也就變成串行了。當SQLite編譯時加了SQLITE_THREADSAFE=1參數時默認啓用。若SQLITE_THREADSAFE不爲0,能夠在初始化SQLite前,調用sqlite3_config(SQLITE_CONFIG_SERIALIZED)啓用;或者在建立數據庫鏈接時,設置SQLITE_OPEN_FULLMUTEX flag。

  而這裏所說的初始化是指調用sqlite3_initialize()函數,這個函數在調用sqlite3_open()時會自動調用,且只有第一次調用是有效的。性能

  調用sqlite3_threadsafe()能夠得到編譯期的SQLITE_THREADSAFE參數。標準發行版是1,也就是串行模式;而iOS上是2,也就是多線程模式;Python的sqlite3模塊也默認使用串行模式,能夠用sqlite3.threadsafety來配置。

  

  另外一個要說明的是prepared statement,它是由數據庫鏈接(的pager)來管理的,使用它也可當作使用這個數據庫鏈接。所以在多線程模式下,併發對同一個數據庫鏈接調用sqlite3_prepare_v2()來建立prepared statement,或者對同一個數據庫鏈接的任何prepared statement併發調用sqlite3_bind_*()和sqlite3_step()等函數都會出錯(在iOS上,該線程會出現EXC_BAD_ACCESS而停止)。這種錯誤無關讀寫,就是隻讀也會出錯。文檔中給出的安全使用規則是:沒有事務正在等待執行,全部prepared statement都被finalized

  可是默認狀況下,一個線程只能使用當前線程打開的數據庫鏈接,除非在鏈接時設置了check_same_thread=False參數。若是是用不一樣的數據庫鏈接,每一個鏈接都不能讀取其餘鏈接中未提交的數據,除非使用read-uncommitted模式。

如今3種模式都有所瞭解了,清楚SQLite並非對多線程無能爲力後,接下來就瞭解下事務吧。

 

2、事務


  數據庫只有在事務中才能被更改。全部更改數據庫的命令(除SELECT之外的全部SQL命令)都會自動開啓一個新事務,而且當最後一個查詢完成時自動提交。
  而BEGIN命令能夠手動開始事務,並關閉自動提交。當下一條COMMIT命令執行時,自動提交再次打開,事務中所作的更改也被寫入數據庫。當COMMIT失敗時,自動提交仍然關閉,以便讓用戶嘗試再次提交。若執行的是ROLLBACK命令,則也打開自動提交,但不保存事務中的更改。關閉數據庫或遇到錯誤時,也會自動回滾事務。
  

  常常有人抱怨SQLite的插入太慢實際上它能夠作到每秒插入幾萬次,可是每秒只能提交幾十次事務。所以在插入大批數據時,能夠經過禁用自動提交來提速。

  還有一個很重要的知識點須要強調:事務是和數據庫鏈接相關的,每一個數據庫鏈接(使用pager來)維護本身的事務,且同時只能有一個事務(可是能夠用SAVEPOINT來實現內嵌事務)。也就是說,事務與線程無關,一個線程裏能夠同時用多個數據庫鏈接來完成多個事務,而多個線程也能夠同時(非併發)使用一個數據庫鏈接來共同完成一個事務。

 

而要實現事務,就不得不用到
一個SQLite數據庫文件有5種鎖的狀態:

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

一個線程只有在擁有低級別的鎖的時候,才能獲取更高一級的鎖。SQLite就是靠這5種類型的鎖,巧妙地實現了讀寫線程的互斥。同時也可看出,寫操做必須進入EXCLUSIVE狀態,此時併發數被降到1,這也是SQLite被認爲併發插入性能很差的緣由。
另外,read-uncommitted和WAL模式會影響這個鎖的機制。在這2種模式下,讀線程不會被寫線程阻塞,即便寫線程持有PENDING或EXCLUSIVE鎖。

提到鎖就不得不說到死鎖的問題,而SQLite也可能出現死鎖。
下面舉個例子:

鏈接1:BEGIN (UNLOCKED)
鏈接1:SELECT ... (SHARED)
鏈接1:INSERT ... (RESERVED)
鏈接2:BEGIN (UNLOCKED)
鏈接2:SELECT ... (SHARED)
鏈接1:COMMIT (PENDING,嘗試獲取EXCLUSIVE鎖,但還有SHARED鎖未釋放,返回SQLITE_BUSY)
鏈接2:INSERT ... (嘗試獲取RESERVED鎖,但已有PENDING鎖未釋放,返回SQLITE_BUSY)

如今2個鏈接都在等待對方釋放鎖,因而就死鎖了。固然,實際狀況並沒那麼糟糕,任何一方選擇不繼續等待,回滾事務就好了。

不過要更好地解決這個問題,就必須更深刻地瞭解事務了。
實際上BEGIN語句能夠有3種起始狀態:

  • DEFERRED:默認值,開始事務時不獲取任何鎖。進行第一次讀操做時獲取SHARED鎖,進行第一次寫操做時獲取RESERVED鎖。
  • IMMEDIATE:開始事務時獲取RESERVED鎖。
  • EXCLUSIVE:開始事務時獲取EXCLUSIVE鎖。


如今考慮2個事務在開始時都使用IMMEDIATE方式:

鏈接1:BEGIN IMMEDIATE (RESERVED)
鏈接1:SELECT ... (RESERVED)
鏈接1:INSERT ... (RESERVED)
鏈接2:BEGIN IMMEDIATE (嘗試獲取RESERVED鎖,但已有RESERVED鎖未釋放,所以事務開始失敗,返回SQLITE_BUSY,等待用戶重試)
鏈接1:COMMIT (EXCLUSIVE,寫入完成後釋放)
鏈接2:BEGIN IMMEDIATE (RESERVED)
鏈接2:SELECT ... (RESERVED)
鏈接2:INSERT ... (RESERVED)
鏈接2:COMMIT (EXCLUSIVE,寫入完成後釋放)

這樣死鎖就被避免了。

而EXCLUSIVE方式則更爲嚴苛,即便其餘鏈接以DEFERRED方式開啓事務也不會死鎖:

鏈接1:BEGIN EXCLUSIVE (EXCLUSIVE)
鏈接1:SELECT ... (EXCLUSIVE)
鏈接1:INSERT ... (EXCLUSIVE)
鏈接2:BEGIN (UNLOCKED)
鏈接2:SELECT ... (嘗試獲取SHARED鎖,但已有EXCLUSIVE鎖未釋放,返回SQLITE_BUSY,等待用戶重試)
鏈接1:COMMIT (EXCLUSIVE,寫入完成後釋放)
鏈接2:SELECT ... (SHARED)
鏈接2:INSERT ... (RESERVED)
鏈接2:COMMIT (EXCLUSIVE,寫入完成後釋放)

不過在併發很高的狀況下,直接獲取EXCLUSIVE鎖的難度比較大;並且爲了不EXCLUSIVE狀態長期阻塞其餘請求,最好的方式仍是讓全部寫事務都以IMMEDIATE方式開始。
順帶一提,要實現重試的話,可使用sqlite3_busy_timeout()或sqlite3_busy_handler()函數。

因而可知,要想保證線程安全的話,能夠有這4種方式:

  1. SQLite使用單線程模式,用一個專門的線程訪問數據庫。
  2. SQLite使用單線程模式,用一個線程隊列來訪問數據庫,隊列一次只容許一個線程執行,隊列裏的線程共用一個數據庫鏈接。
  3. SQLite使用多線程模式,每一個線程建立本身的數據庫鏈接。
  4. SQLite使用串行模式,全部線程共用全局的數據庫鏈接。

 

3、sqlite3插入速度慢

 

1.像上述同樣顯示的給多個insert加上事務

  sqlite在沒有顯式使用事務的時候會爲每條insert都使用事務操做,而sqlite數據庫是以文件的形式存在磁盤中,就至關於每次訪問時都要打開一次文件,若是對數據進行大量的操做,時間都耗費在I/O操做上,因此很慢。

解決方法是顯式使用事務的形式提交:由於咱們開始事務後,進行的大量操做的語句都保存在內存中,當提交時才所有寫入數據庫,此時,數據庫文件也就只用打開一次。

 

2.若是加上事務仍是不行,能夠嘗試修改同步模式

  初用sqlite3插入數據時,插入每條數據大概須要100ms左右。若是是批量導入,能夠引進事務提升速度。可是假設你的業務是每間隔幾秒插入幾條數據,顯然100ms是不能允許的。
解決辦法是,在調用sqlite3_open函數後添加下面一行代碼:

    sqlite3_exec(db, "PRAGMA synchronous = OFF; ", 0,0,0);

 

    上面的解決辦法貌似治標不治本,爲何加上上面的代碼行,速度會提升那麼多?

 

磁盤同步 

1.如何設置:

PRAGMA synchronous = FULL; (2) 

PRAGMA synchronous = NORMAL; (1) 

PRAGMA synchronous = OFF; (0)
 

2.參數含義:

當synchronous設置爲FULL (2), SQLite數據庫引擎在緊急時刻會暫停以肯定數據已經寫入磁盤。這使系統崩潰或電源出問題時能確保數據庫在重起後不會損壞。FULL synchronous很安全但很慢。
 

當synchronous設置爲NORMAL(1), SQLite數據庫引擎在大部分緊急時刻會暫停,但不像FULL模式下那麼頻繁。 NORMAL模式下有很小的概率(但不是不存在)發生電源故障致使數據庫損壞的狀況。但實際上,在這種狀況 下極可能你的硬盤已經不能使用,或者發生了其餘的不可恢復的硬件錯誤。
 

設置爲synchronous OFF (0)時,SQLite在傳遞數據給系統之後直接繼續而不暫停。若運行SQLite的應用程序崩潰, 數據不會損傷,但在系統崩潰或寫入數據時意外斷電的狀況下數據庫可能會損壞。另外一方面,在synchronous OFF時 一些操做可能會快50倍甚至更多。在SQLite 2中,缺省值爲NORMAL.而在3中修改成FULL。  www.2cto.com  
 

3.建議:

若是有按期備份的機制,並且少許數據丟失可接受,用OFF。

     注意上面紅色加粗的字樣。總結:若是你的數據對安全性完整性等要求不是過高,能夠採用設置爲0的方法,畢竟只是「數據庫可能會損壞」,至於損壞概率爲多大,筆者也暫不知曉。。。。。。還沒遇到過損壞,不知何時纔會發生。

相關文章
相關標籤/搜索