提升SQLite數(shù)據(jù)插入效率低、速度慢的方法

前言

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ù)插入效率的完整方法。(大神們勿噴)

1 數(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)

2慢速——最粗暴的方法

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;

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容