前言
SQLite數(shù)據(jù)庫由于其簡單、靈活、輕量、開源,已經(jīng)被越來越多的被應(yīng)用到中小型應(yīng)用中。甚至有人說,SQLite完全可以用來取代c語言中的文件讀寫操作。因此我最近編寫有關(guān)遙感數(shù)據(jù)處理的程序的時候,也將SQLite引入進來,以提高數(shù)據(jù)的結(jié)構(gòu)化程度,并且提高大數(shù)據(jù)的處理能力(SQLite最高支持2PB大小的數(shù)據(jù))。但是最開始,我發(fā)現(xiàn),直接使用SQL語句的插入效率簡直低的令人發(fā)指的。后來不斷查文檔、查資料,才發(fā)現(xiàn)了一條快速的“數(shù)據(jù)插入”之路。本文就以插入數(shù)據(jù)為例,整合網(wǎng)上和資料書中的各種提高SQLite效率的方法,給出提高SQLite數(shù)據(jù)插入效率的完整方法。(大神們勿噴)
我使用的電腦是Win7 64位系統(tǒng),使用VC2010編譯,SQLIte版本為3.7.15.2 ,電腦CPU為二代i3處理器,內(nèi)存6G。
實驗之前,先建立要插入數(shù)據(jù)的表:
[sql]
createtablet1?(idinteger,?xinteger,?yinteger,?weightreal)
SQLite的API中直接執(zhí)行SQL的函數(shù)是:
intsqlite3_exec(??sqlite3*,constchar*sql,int(*callback)(void*,int,char**,char**),void*,char**errmsg)
直接使用INSERT語句的字符串進行插入,程序部分代碼(完整代碼見后文),如下:
for(inti=0;i
{
std::stringstream?ssm;
ssm<<"insert?into?t1?values("<
sqlite3_exec(db,ssm.str().c_str(),0,0,0);
}
這個程序運行的太慢了,我已經(jīng)沒時間等待了,估算了一下,基本上是7.826條/s
3中速——顯式開啟事務(wù)
所謂”事務(wù)“就是指一組SQL命令,這些命令要么一起執(zhí)行,要么都不被執(zhí)行。在SQLite中,每調(diào)用一次sqlite3_exec()函數(shù),就會隱式地開啟了一個事務(wù),如果插入一條數(shù)據(jù),就調(diào)用該函數(shù)一次,事務(wù)就會被反復(fù)地開啟、關(guān)閉,會增大IO量。如果在插入數(shù)據(jù)前顯式開啟事務(wù),插入后再一起提交,則會大大提高IO效率,進而加數(shù)據(jù)快插入速度。
開啟事務(wù)只需在上述代碼的前后各加一句開啟與提交事務(wù)的命令即可:
sqlite3_exec(db,"begin;",0,0,0);
for(inti=0;i
{
std::stringstream?ssm;
ssm<<"insert?into?t1?values("<
sqlite3_exec(db,ssm.str().c_str(),0,0,0);
}
sqlite3_exec(db,"commit;",0,0,0);
顯式開啟事務(wù)后,這個程序運行起來明顯快很多,估算效率達到了34095條/s,較原始方法提升約5000倍。
4高速——寫同步(synchronous)
我要使用一個遙感處理算法處理10000*10000的影像,中間有一步需要插入100000000條數(shù)據(jù)到數(shù)據(jù)庫中,如果按照開啟事務(wù)后的速度34095條/s,則需要100000000÷34095 =?2932秒 = 48.9分,仍然不能夠接受,所以我接著找提升速度的方法。終于,在有關(guān)講解SQLite配置的資料中,看到了“寫同步”選項。
在SQLite中,數(shù)據(jù)庫配置的參數(shù)都由編譯指示(pragma)來實現(xiàn)的,而其中synchronous選項有三種可選狀態(tài),分別是full、normal、off。這篇博客以及官方文檔里面有詳細講到這三種參數(shù)的設(shè)置。簡要說來,full寫入速度最慢,但保證數(shù)據(jù)是安全的,不受斷電、系統(tǒng)崩潰等影響,而off可以加速數(shù)據(jù)庫的一些操作,但如果系統(tǒng)崩潰或斷電,則數(shù)據(jù)庫可能會損毀。
SQLite3中,該選項的默認值就是full,如果我們再插入數(shù)據(jù)前將其改為off,則會提高效率。如果僅僅將SQLite當做一種臨時數(shù)據(jù)庫的話,完全沒必要設(shè)置為full。在代碼中,設(shè)置方法就是在打開數(shù)據(jù)庫之后,直接插入以下語句:
sqlite3_exec(db,"PRAGMA?synchronous?=?OFF;?",0,0,0);
此時,經(jīng)過測試,插入速度已經(jīng)變成了41851條/s,也就是說,插入100000000條數(shù)據(jù),需要2389秒 = 39.8分。
5極速——執(zhí)行準備
雖然寫同步設(shè)為off后,速度又有小幅提升,但是仍然較慢。我又一次踏上了尋找提高SQLite插入效率方法的道路上。終于,我發(fā)現(xiàn),SQLite執(zhí)行SQL語句的時候,有兩種方式:一種是使用前文提到的函數(shù)sqlite3_exec(),該函數(shù)直接調(diào)用包含SQL語句的字符串;另一種方法就是“執(zhí)行準備”(類似于存儲過程)操作,即先將SQL語句編譯好,然后再一步一步(或一行一行)地執(zhí)行。如果采用前者的話,就算開起了事務(wù),SQLite仍然要對循環(huán)中每一句SQL語句進行“詞法分析”和“語法分析”,這對于同時插入大量數(shù)據(jù)的操作來說,簡直就是浪費時間。因此,要進一步提高插入效率的話,就應(yīng)該使用后者。
“執(zhí)行準備”主要分為三大步驟:
1.調(diào)用函數(shù)
intsqlite3_prepare_v2(?sqlite3?*db,constchar*zSql,intnByte,??sqlite3_stmt?**ppStmt,constchar**pzTail);
并且聲明一個指向sqlite3_stmt對象的指針,該函數(shù)對參數(shù)化的SQL語句zSql進行編譯,將編譯后的狀態(tài)存入ppStmt中。
2.調(diào)用函數(shù) sqlite3_step() ,這個函數(shù)就是執(zhí)行一步(本例中就是插入一行),如果函數(shù)返回的是SQLite_ROW則說明仍在繼續(xù)執(zhí)行,否則則說明已經(jīng)執(zhí)行完所有操作;
3.調(diào)用函數(shù) sqlite3_finalize(),關(guān)閉語句。
關(guān)于執(zhí)行準備的API的具體語法,詳見官方文檔。本文中執(zhí)行準備的c++代碼如下:
sqlite3_exec(db,"begin;",0,0,0);
sqlite3_stmt?*stmt;
constchar*?sql?="insert?into?t1?values(?,?,?,?)";
sqlite3_prepare_v2(db,sql,strlen(sql),&stmt,0);
for(inti=0;i
{
sqlite3_reset(stmt);
sqlite3_bind_int(stmt,1,i);
sqlite3_bind_int(stmt,1,i*2);
sqlite3_bind_int(stmt,1,i/2);
sqlite3_bind_double(stmt,1,i*i);
}
sqlite3_finalize(stmt);
sqlite3_exec(db,"commit;",0,0,0);
此時測試數(shù)據(jù)插入效率為:265816條/s,也就是說,插入100000000條數(shù)據(jù),需要376秒 = 6.27分。這個速度已經(jīng)很滿意了。
5 總結(jié)
綜上所述啊,SQLite插入數(shù)據(jù)效率最快的方式就是:事務(wù)+關(guān)閉寫同步+執(zhí)行準備(存儲過程),如果對數(shù)據(jù)庫安全性有要求的話,就開啟寫同步。
參考資料:
1. SQLite官方文檔:http://www.sqlite.org/docs.html
2.《解決sqlite3插入數(shù)據(jù)很慢的問題》:http://blog.csdn.net/victoryknight/article/details/7461703
3.《The Definitive Guide to SQLite》Apress出版:http://www.apress.com/9781430232254(這是本好書)
附最終完整代碼:
#include?
#include?
#include?
#include?
#include?"sqlite3.h"
constintnCount?=?500000;
intmain?(intargc,char**?argv)
{
sqlite3*?db;
sqlite3_open("testdb.db",&db);
sqlite3_exec(db,"PRAGMA?synchronous?=?OFF;?",0,0,0);
sqlite3_exec(db,"drop?table?if?exists?t1",0,0,0);
sqlite3_exec(db,"create?table?t1(id?integer,x?integer,y?integer?,weight?real)",0,0,0);
clock_tt1?=?clock();
sqlite3_exec(db,"begin;",0,0,0);
sqlite3_stmt?*stmt;
constchar*?sql?="insert?into?t1?values(?,?,?,?)";
sqlite3_prepare_v2(db,sql,strlen(sql),&stmt,0);
for(inti=0;i
{
//?std::stringstream?ssm;
//?ssm<<"insert?into?t1?values("<
//?sqlite3_exec(db,ssm.str().c_str(),0,0,0);
sqlite3_reset(stmt);
sqlite3_bind_int(stmt,1,i);
sqlite3_bind_int(stmt,2,i*2);
sqlite3_bind_int(stmt,3,i/2);
sqlite3_bind_double(stmt,4,i*i);
sqlite3_step(stmt);
}
sqlite3_finalize(stmt);
sqlite3_exec(db,"commit;",0,0,0);
clock_tt2?=?clock();
sqlite3_close(db);
std::cout<<"cost?tima:?"<<(t2-t1)/1000.<<"s"<
return0;
}