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
MacOSX的測試結果: