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