測試目標
獲取SQlite的常規性能指標
測試環境
CPU:8核,Intel(R) Xeon(R) CPU E5-2430 0 @ 2.20GHz
內存:16G
磁盤:SSD
Linux 2.6.32
SQlite最新版本3.8.11
測試場景
1) 主鍵查詢測試
2) 主鍵更新測試
3) 批量導入測試
初始化
1) 測試表結構
CREATE TABLE user( id integer primary key autoincrement, c1 int, c2 varchar(1000), c3 varchar(1000));
CREATE TABLE orders( id integer primary key autoincrement, user_id int, c1 varchar(1000), c2 varchar(1000));
2) 初始化數據
通過程序往user表和orders表中導入10w條記錄,整個db文件在400M左右。
3) 測試說明
sqlite本身通過PRAGMA命令可以設置程序緩存大小( cache_size),但同時sqlite的緩存策略中並沒有忽略操作系統緩存的影響,因此本文的測試結果使用默認的cache_size(2000個page),通過多次測試取平均值,來得到一個大概的性能指標。此外,sqlite主要用於嵌入式設備,而本文的測試基於PC,因此測試數據僅作參考。
單表主鍵查詢
1) 測試說明
該項測試主要測試主鍵查詢的性能,測試語句形如:
“select * from user where id = xxx”,xxx通過隨機函數生成,由於生成的測試數據id的范圍是[1-100000],通過隨機函數生成[1-1000000]的隨機數,基本能保證1%的命中率(實際測試中得到印證)。Sqlite支持讀並發,因此該項測試測試了多線程並發情況下的性能,測試結果的時間單位為毫秒(ms)。多線程測試模型很簡單,每個線程執行同樣的查詢10w次,計算總耗時時間,然后根據平均值與時間的比值,計算出QPS和TPS,通過參數SQLITE_OPEN_SHAREDCACHE控制是否啟用共享緩存模式。
2) 測試結果
a) 非共享緩存模式
線程數目 |
1 |
2 |
4 |
8 |
16 |
第一輪 |
2886 |
3641 |
8392 |
19615 |
27875 |
第二輪 |
2867 |
3933 |
8088 |
21010 |
28635 |
第三輪 |
2821 |
4131 |
8077 |
21220 |
28689 |
第四輪 |
2941 |
4011 |
7787 |
20983 |
27965 |
第五輪 |
2896 |
3724 |
7881 |
21332 |
28654 |
平均值 |
2881 |
3949 |
7958 |
21136 |
28363 |
CPU% |
80% |
180% |
320% |
670% |
710% |
QPS |
3.4w |
5w |
5w |
3.8w |
5.6w |
表一
b) 共享緩存模式
線程數目 |
1 |
2 |
4 |
第一輪 |
3050 |
12616 |
26554 |
第二輪 |
3077 |
12331 |
26396 |
第三輪 |
3131 |
12327 |
27070 |
第四輪 |
3096 |
13014 |
27031 |
第五輪 |
2972 |
12866 |
27778 |
平均值 |
3065 |
12634 |
26965 |
CPU% |
80% |
120% |
120% |
QPS |
3.3w |
1.5w |
1.4w |
表二
3) 結果分析
從表一結果來,隨着並發度提升,主機CPU利用率也隨着上升;QPS由單線程3.4w,上升到4線程並發5w左右,但是到8線程又出現了一定的回落,16線程並發時,QPS又回到5w左右。測試過程中,通過觀察CPU利用率和磁盤IO,基本上可以斷定是CPU限制了QPS的上升。因為主機CPU核數為8核,因此CPU的利用率在高並發下可以接近800%,基本上已經到達極限。當然,從絕對值來看每秒5w的查詢性能,也確實很不錯!
從表二結果來看,設置共享緩存模式后,並發性能有很大的下降,從CPU利用率就可見一斑,QPS由單線程3.3w降低到8線程1.4w左右。關於這一點我一直很疑惑,為啥開了共享緩存后,並發性能還下降了。通過在程序運行過程中抓取堆棧並結合源碼找到了原因,並發查詢時,大量的線程會堵塞在sqlite3BtreeEnter函數中的mutex里面。共享內存模式下,進程內的多個線程通過共享同一個B樹對象,達到共享內存的目的,B樹對象通過一個mutex保護,正是由於這個mutex的競爭,導致並發度嚴重下降。所以共享內存模式雖然能減少內存的使用,但是以犧牲並發性能為代價的。
上面的測試實際上是多線程模式下的共享和非共享模式下的測試結果。實際上使用sqlite連接有幾種方式:單線程模式,多線程串行化模式和多線程模式,一般情況下,我們會選擇多線程模式,這幾種模式可以編譯階段指定,也可以在打開數據庫連接時指定,還可以在運行時指定。
(1)編譯階段
這幾種模式可以通過參數SQLITE_THREADSAFE在編譯階段指定,可以取值0,1,2,默認是1。這三種取值的含義如下:
0:單線程模式,即內部不做mutex保護,多線程運行sqlite不安全。
1:多線程的串行模式,sqlite幫助多線程實現串行化。
2:多線程的並發模式,要求同一個時刻,同一個連接不被多個線程使用。
(2)打開數據庫階段
除了可以在編譯階段指定運行模式,還可以在打開數據庫時(sqlite3_open_v2())通過參數指定,主要的幾個參數以及含義如下:
SQLITE_OPEN_NOMUTEX: 設置數據庫連接運行在多線程模式(沒有指定單線程模式的情況下)
SQLITE_OPEN_FULLMUTEX:設置數據庫連接運行在串行模式。
SQLITE_OPEN_SHAREDCACHE:設置運行在共享緩存模式。
SQLITE_OPEN_PRIVATECACHE:設置運行在非共享緩存模式。
SQLITE_OPEN_READWRITE:指定數據庫連接可以讀寫。
SQLITE_OPEN_CREATE:如果數據庫不存在,則創建。
(3)運行時階段
通過調用sqlite3_config接口,也可以設置運行模式。
若編譯參數SQLITE_THREADSAFE=1 or SQLITE_THREADSAFE=2,那么可以在運行時設置線程模式。
SQLITE_CONFIG_SINGLETHREAD:單線程模式
SQLITE_CONFIG_MULTITHREAD:多線程模式,應用層保證同一個時刻,同一個連接只有一個線程使用。
SQLITE_CONFIG_SERIALIZED:串行模式,sqlite幫助多線程實現串行化。
批量載入測試
1) 測試說明
導入數據是db最常用的一個功能,該項測試主要測試了3種模式的導入性能,單行單事務,多行事務和prepare模式的多行事務。主要模型如下:
a) 單行單事務
begin
insert into user values(1,’xxx’); commit; begin
insert into user values(1,’xxx’); commit; ……
b) 多行單事務
begin
insert into user values(1,’xxx’);insert into user values(2,’xxx’);…… commit;
c) prepare綁定
begin
prepare insert into user(id, c1) values(?,?); bind (id,c1) …… commit;
2) 測試結果
|
單行事務 |
10w行事務 |
10w行事務 (prepare) |
第一輪 |
1693533 |
11856 |
9079 |
第二輪 |
1673983 |
11667 |
8375 |
第三輪 |
略 |
12075 |
8566 |
第四輪 |
略 |
11611 |
8773 |
第五輪 |
略 |
11331 |
8660 |
平均值 |
|
11671 |
8593 |
TPS |
60 |
8568 |
1.16w |
表三
3) 結果分析
從測試結果來看,單行事務和多行事務差別非常大,這也充分說明了,對於db而言,事務提交動作是非常耗時的。單行事務TPS只有60,而10w行事務TPS則達到了8500,有超過100倍的提升。與傳統DBMS一樣,sqlite提交事務時,也需要進行較慢的刷盤動作,因此刷1次盤與刷10w次盤,性能差別非常大。第三欄是prepare類型的事務,也是采用了10w行作為一個事務單位,但效果會更優。這主要原因是采用prepare模型事務,10w行記錄只需要解析1次,而前者需要解析10w次,雖然解析時間不長,但積少成多,所以第三欄僅僅這一個優化點,就將TPS從8500提升到1.16w。
主鍵更新
1) 測試說明
本測試用例的語句也非常簡單,就是簡單的主鍵更新,將列值自增1。測試語句形如:update user set c1=c1+1 where id=xxx。SQLite不支持並發更新,因此測試寫都是單線程。分別模擬單行事務,多行事務,觀察SQLite的更新性能。統計更新1w行,程序執行的時間,並根據更新記錄數目與執行時間計算TPS。
2) 測試結果
|
單行事務 |
1000行事務 |
1w行事務 |
第一輪 |
164784 |
16623 |
16232 |
第二輪 |
170256 |
16382 |
17514 |
第三輪 |
166387 |
17099 |
17696 |
第四輪 |
172987 |
17030 |
17753 |
第五輪 |
166543 |
16386 |
17787 |
平均值 |
169043 |
16724 |
17832 |
TPS |
59 |
598 |
560.7 |
表四
3) 結果分析
關於多行事務這一塊,基本與導入操作類似,多行事務可以顯著提高性能。同時,也要看到更新的TPS相比插入的TPS要相差很多。個人推斷這個現象與磁盤IO有莫大關系,因為插入時,由於主鍵自增,寫都是順序寫;而本測例的更新都是隨機更新,而且產生的臟頁遠遠大於cache_size,一定伴隨着大量的隨機寫,導致更新性能比較差。