SQLite性能 - 它不是內存數據庫,不要對IN-MEMORY望文生意。

SQLite建立的數據庫有一種模式IN-MEMORY,可是它並不表示SQLite就成了一個內存數據庫。IN-MEMORY模式能夠簡單地理解爲,原本建立的數據庫文件是基於磁盤的,如今整個文件使用內存空間來代替磁盤空間,其它操做保持一致。也就是數據庫的設計沒有根本改變。html

提到內存,許多人就會簡單地理解爲,內存比磁盤速度快不少,因此內存模式比磁盤模式的數據庫速度也快不少,甚至有人望文生意就把它變成等同於內存數據庫。linux

它並非爲內存數據庫應用而設計的,本質仍是文件數據庫。它的數據庫存儲文件有將近一半的空間是空置的,這是它的B樹存儲決定的,請參看上一篇SQLite存儲格式。內存模式只是將數據庫存儲文件放入內存空間,但並不考慮最有效管理你的內存空間,其它臨時文件也要使用內存,事務回滾日誌同樣要生成,只是使用了內存空間。它的做用應該偏向於臨時性的用途。git

咱們先來看一下下面的測試結果,分別往memory和disk模式的sqlite數據庫進行1w, 10w以及100w條數據的插入,採用一次性提交事務。另外使用commit_hook捕捉事務提交次數。github

(注:測試場景爲在新建的數據庫作插入操做,因此回滾日誌是很小的,而且無須要在插入過程當中查找而從數據庫加載頁面,所以測試也並不全面)sql

內存模式數據庫

磁盤模式macos

在事務提交前的耗時 (事務提交後的總耗時):windows

  1w 10w 100w
內存模式 0.04s 0.35s 3.60s
磁盤模式 0.06s (0.27s) 0.47s (0.72s) 3.95s (4.62s)

 

 

 

能夠看到當操做的數據越少時,內存模式的性能提升得越明顯,事務IO的同步時間消耗越顯注。ide

上圖還有一組數據比較,就是在單次事務提交中,若是要爲每條插入語句準備的話性能

  1w 10w 100w
內存模式 0.19s 1.92s 19.46s
磁盤模式 0.21s (0.35s) 2.06s (2.26s) 19.88s (20.41s)

 

 

 

咱們從SQLite的設計來分析,一次插入操做,SQLite到底作了些什麼。首先SQLite的數據庫操做是以頁面大小爲單位的。在單條記錄插入的事務中,回滾日誌文件被建立。在B樹中查找目標頁面,要讀入一些頁面,而後將目標頁面以及要修改的父級頁面寫出到回滾日誌。操做目標頁面的內存映像,插入一條記錄,並在頁面內重排序(索引排序,無索引作自增計數排序,參看上一篇《SQLite數據庫存儲格式》)。最後事務提交將修改的頁面寫出到數據庫文件,成功後再刪除日誌文件。在這過程當中顯式進行了2次寫磁盤(1次寫日誌文件,1次同步寫數據庫),還有2次隱式寫磁盤(日誌文件的建立和刪除),這是在操做目錄節點。以及爲查找加載的頁面讀操做。更加詳細能夠參看官方文檔的討論章節《Atomic Commit In SQLite》

若是假設插入100條記錄,每條記錄都要提交一次事務就很不划算,因此須要批量操做來減小事務提交次數。假設頁面大小爲4KB,記錄長度在20字節內,每頁可放多於200條記錄,一次事務提交插入100條記錄,假設這100條記錄正好能放入到同一頁面又沒有產生頁面分裂,這樣就能夠在單條記錄插入事務的IO開銷耗損代價中完成100條記錄插入。

當咱們的事務中,插入的數據越多,事務的IO代價就會攤得越薄,因此在插入100w條記錄的測試結果中,內存模式和磁盤模式的耗時都十分接近。實際應用場合中也不多會須要一次插入100w的數據。有這樣的須要就不要考慮SQLite。

(補充說明一下,事務IO指代同步數據庫的IO,以及回滾日誌的IO,只在本文使用)

 

除了IO外,還有沒有其它地方也影響着性能。那就是語句執行。其實反觀一切,都是在對循環進行優化。

for (i = 0; i < repeat; ++i)
{
    exec("BEGIN TRANS");
    exec("INSERT INTO ...");
    exec("END TRANS");
}

批量插入:

exec("BEGIN TRANS");
for (i = 0; i < repeat; ++i)
{
    exec("INSERT INTO ...");
}
exec("END TRANS");

當咱們展開插入語句的執行

exec("BEGIN TRANS");
for (i = 0; i < repeat; ++i)
{
    // unwind exec("INSERT INTO ...");
    prepare("INSERT INTO ...");
    bind();
    step();
    finalize();
}
exec("END TRANS");

又發現循環內能夠移出部分語句

exec("BEGIN TRANS");
// unwind exec("INSERT INTO ...");
prepare("INSERT INTO ...");
for (i = 0; i < repeat; ++i)
{
    bind();
    step();
}
finalize();
exec("END TRANS");

這樣就獲得了批量插入的最終優化模式。

因此對sql語句的分析,編譯和釋放是直接在損耗CPU,而同步IO則是在飢餓CPU。

請看下圖

分別爲內存模式1w和10w兩組測試,每組測試包括4項測試

1.只編譯一條語句,只提交一次事務

2.每次插入編譯語句,只提交一次事務

3.只編譯一條語句,但使用自動事務。

4.每次插入編譯語句,並使用自動事務。

能夠看到測試項目4基本上就是測試項目2和測試項目3的結果的和。

測試項目1就是批量插入優化的最終結果。

 

 

下面是探討內存模式的使用:

通過上面的分析,內存模式在批量插入對比磁盤模式提高不是太顯注的,請如今開始關注未批量插入的結果。

下面給出的是磁盤模式0.1w和0.2w兩組測試,每組測試包括4項測試

能夠看到在非批量插入狀況,sqlite表現不好要100秒來完成1000次單條插入事務,但絕非sqlite很吃力,由於cpu在空載,IO阻塞了程序。

再來看內存模式20w測試

能夠看到sqlite在內存模式,即便在20w次的單條插入事務,其耗時也不太遜於磁盤模式100w插入一次事務。

  0.1w 0.2w 20w
內存模式(非批量插入)     15.87s
磁盤模式(非批量插入) 97.4s 198.28s  

 

 

 

  編譯1次插入語句 每次插入編譯1次語句
內存模式(20w,20w次事務) 11.10s 15.87s
磁盤模式(100w,1次事務) 4.62s 20.41s

 

 

 

 

 不能給出內存模式100w次事務的測試結果是由於程序運行出問題。

在100w的插入一次性事務測試結果,內存模式和磁盤模式相差不到1秒,這1秒就是最後大量數據庫同步到數據庫的IO時間。

再回到上面兩圖兩表的測試結果,磁盤模式在執行多事務顯得偏癱,每秒很少於10個單條插入的事務。而內存模式下執行事務的能力仍然堅挺,每秒1w次單條插入的事務也不在話下。

在實際應用中,數據隨機實時,你又不想作批量插入控制,就能夠考慮用內存模式將現有的數據立刻用事務提交,無論事務提交的數據是可能是少。你只要定製計劃,將內存模式的數據庫同步到你的外部數據庫。由於每一個內存模式的數據庫是獨立的,你同步一個內存模式的數據庫到外部的期間,就能夠同時使用另外一個內存模式的數據庫緩衝數據。

(上面刪除段落是根據MinGW系統的測試結果。在真機環境測試了win 7 32bit和win 7 64bit,以及在它們之上使用mingw系統,測試結果是sqlite處理1000個單條插入事務總耗時在100秒級別。而在vm環境,vm虛擬磁盤上測試了xp,linux和macosx,測試結果是sqlite處理1000個單條插入事務總耗時在10秒級別內。值得注意的是,vm虛擬磁盤不是直接操做磁盤,因此我還要另找磁盤,掛接真實的磁盤對虛擬機環境進行測試。)

更多磁盤模式的測試結果在下一篇《意想不到,但又情理之中的測試結果》。


在通過慎重考慮後,在linux和mac環境下進行了測試,驗證了一句「數據庫都構建在痛苦的操做系統之上」。上面的測試環境是MinGw,痛苦的不是windows而是在windows之上加上的一層MinGw系統,磁盤操做十分痛苦。根據在linux和mac環境的測試結果,內存模式和磁盤模式在單條插入自動事務的性能更加接近,相差只有10倍左右,因爲不用在MinGW這樣的適配系統痛苦地操做磁盤,因此在其它批量插入事務的測試項目中,兩種模式的測試結果更加趨於接近。

至於你想用sqlite的內存模式做持久用途或者去媲美內存數據庫,可能不是正確的選擇。sqlite是一個體積輕巧,能夠幫你管理關係型數據的嵌入式數據庫。它適應嵌入式的空間小,耗電低和佔用內存有限的特殊環境。它的高效是不由於它的簡單,而在基本的數據庫查詢功能上有打折扣。它在設計上有針對性的取捨,使它更適合某些應用場合,也必然在舍的部分蹩足。

 

本篇至此結束,謝謝觀看。

後續會有":memory:","file:whereIsDb?mode=memory"以及"disk.db"這三種模式的對比。

 

測試代碼在https://github.com/bbqz007/xw/test.sqlite.in-memory.zip。(不支持VC,須要自行下載編譯sqlite。支持VC編譯的測試代碼未上傳。)

mingw測試插入1000條數據使用自動事務,即一共提交1000次事務:

運行在      總耗時

xp (vm11)            9s

win 7 64bit        200s

win 7 32bit        100s

最後補上Linux (vm11)和MacOSX (vm11)的測試結果:

Linux 2.6.32-358.el6.x86_64
cpu MHz        : 3591.699
cpu MHz        : 3591.699
----- 10000 in memory ----
repeat insert 10000 times, in 1 trans, with 1 stmt prepared
 0.04s
 0.04s
commit: 1
repeat insert 10000 times, in 1 trans, with each stmt prepared
 0.06s
 0.06s
commit: 1
repeat insert 10000 times, in auto trans(s), with 1 stmt prepared
 0.02s
 0.02s
commit: 10000
repeat insert 10000 times, in auto trans(s), with each stmt prepared
 0.06s
 0.06s
commit: 10000
---- 100000 in memory ----
repeat insert 100000 times, in 1 trans, with 1 stmt prepared
 0.11s
 0.11s
commit: 1
repeat insert 100000 times, in 1 trans, with each stmt prepared
 0.40s
 0.40s
commit: 1
repeat insert 100000 times, in auto trans(s), with 1 stmt prepared
 1.28s
 1.28s
commit: 100000
repeat insert 100000 times, in auto trans(s), with each stmt prepared
 1.76s
 1.76s
commit: 100000
---- 200000 in memory ----
repeat insert 200000 times, in 1 trans, with 1 stmt prepared
 0.23s
 0.23s
commit: 1
repeat insert 200000 times, in 1 trans, with each stmt prepared
 0.87s
 0.87s
commit: 1
repeat insert 200000 times, in auto trans(s), with 1 stmt prepared
 7.35s
 7.35s
commit: 200000
repeat insert 200000 times, in auto trans(s), with each stmt prepared
 9.10s
 9.10s
commit: 200000
--- 1000000 in memory ----
repeat insert 1000000 times, in 1 trans, with 1 stmt prepared
 1.23s
 1.23s
commit: 1
repeat insert 1000000 times, in 1 trans, with each stmt prepared
 4.39s
 4.39s
commit: 1
 
 
------ 1000 in disk ----
rm: 沒法刪除"test.db": 沒有那個文件或目錄
repeat insert 1000 times, in 1 trans, with 1 stmt prepared
 0.00s
 0.00s
commit: 1
repeat insert 1000 times, in 1 trans, with each stmt prepared
 0.00s
 0.00s
commit: 1
repeat insert 1000 times, in auto trans(s), with 1 stmt prepared
 0.80s
 0.80s
commit: 1000
repeat insert 1000 times, in auto trans(s), with each stmt prepared
 0.87s
 0.87s
commit: 1000
------ 2000 in disk ----
repeat insert 2000 times, in 1 trans, with 1 stmt prepared
 0.00s
 0.00s
commit: 1
repeat insert 2000 times, in 1 trans, with each stmt prepared
 0.01s
 0.02s
commit: 1
repeat insert 2000 times, in auto trans(s), with 1 stmt prepared
 1.60s
 1.60s
commit: 2000
repeat insert 2000 times, in auto trans(s), with each stmt prepared
 2.27s
 2.27s
commit: 2000
----- 10000 in disk ----
repeat insert 10000 times, in 1 trans, with 1 stmt prepared
 0.01s
 0.02s
commit: 1
repeat insert 10000 times, in 1 trans, with each stmt prepared
 0.04s
 0.04s
commit: 1
---- 100000 in disk ----
repeat insert 100000 times, in 1 trans, with 1 stmt prepared
 0.11s
 0.11s
commit: 1
repeat insert 100000 times, in 1 trans, with each stmt prepared
 0.45s
 0.45s
commit: 1
--- 1000000 in disk ----
repeat insert 1000000 times, in 1 trans, with 1 stmt prepared
 1.27s
 1.34s
commit: 1
repeat insert 1000000 times, in 1 trans, with each stmt prepared
 4.51s
 4.57s
commit: 1
Linux測試結果

MacOSX的測試結果:

相關文章
相關標籤/搜索