提高SQLite數據插入效率低、速度慢的方法(轉)

前言

SQLite數據庫因爲其簡單、靈活、輕量、開源,已經被愈來愈多的被應用到中小型應用中。甚至有人說,SQLite徹底能夠用來取代C語言中的文件讀寫操做。所以我最近編寫有關遙感數據處理的程序的時候,也將SQLite引入進來,以提升數據的結構化程度,而且提升大數據的處理能力(SQLite最高支持2PB大小的數據)。可是最開始,我發現,直接使用SQL語句的插入效率簡直低的使人髮指的。後來不斷查文檔、查資料,才發現了一條快速的「數據插入」之路。本文就以插入數據爲例,整合網上和資料書中的各類提升SQLite效率的方法,給出提升SQLite數據插入效率的完整方法。(大神們勿噴)html

 

1 數據

我使用的電腦是Win7 64位系統,使用VC2010編譯,SQLIte版本爲3.7.15.2 ,電腦CPU爲二代i3處理器,內存6G。
實驗以前,先創建要插入數據的表:
[sql]  view plain copy
 
 
  1. create table t1 (id integer , x integer , y integer, weight real)  
 

2 慢速——最粗暴的方法

SQLite的API中直接執行SQL的函數是:
[cpp]  view plain copy
 
 
  1. int sqlite3_exec(  sqlite3*,    const char *sql,   int (*callback)(void*,int,char**,char**),   void *,   char **errmsg)  
直接使用INSERT語句的字符串進行插入,程序部分代碼(完整代碼見後文),以下:
[cpp]  view plain copy
 
 
  1. for(int i=0;i<nCount;++i)  
  2. {  
  3.     std::stringstream ssm;  
  4.     ssm<<"insert into t1 values("<<i<<","<<i*2<<","<<i/2<<","<<i*i<<")";  
  5.     sqlite3_exec(db,ssm.str().c_str(),0,0,0);  
  6. }  
這個程序運行的太慢了,我已經沒時間等待了,估算了一下,基本上是 7.826 條/s
3 中速——顯式開啓事務
 
所謂」事務「就是指一組SQL命令,這些命令要麼一塊兒執行,要麼都不被執行。在SQLite中,每調用一次sqlite3_exec()函數,就會隱式地開啓了一個事務,若是插入一條數據,就調用該函數一次,事務就會被反覆地開啓、關閉,會增大IO量。若是在插入數據前顯式開啓事務,插入後再一塊兒提交,則會大大提升IO效率,進而加數據快插入速度。
開啓事務只需在上述代碼的先後各加一句開啓與提交事務的命令便可:

 

[cpp]  view plain copy
 
 
  1. sqlite3_exec(db,"begin;",0,0,0);  
  2. for(int i=0;i<nCount;++i)  
  3. {  
  4.     std::stringstream ssm;  
  5.     ssm<<"insert into t1 values("<<i<<","<<i*2<<","<<i/2<<","<<i*i<<")";  
  6.     sqlite3_exec(db,ssm.str().c_str(),0,0,0);  
  7. }  
  8. sqlite3_exec(db,"commit;",0,0,0);  

 

顯式開啓事務後,這個程序運行起來明顯快不少,估算效率達到了34095條/s,較原始方法提高約5000倍。mysql

4 高速——寫同步(synchronous)ios

我要使用一個遙感處理算法處理10000*10000的影像,中間有一步須要插入100000000條數據到數據庫中,若是按照開啓事務後的速度34095條/s,則須要100000000÷34095 = 2932秒 = 48.9分,仍然不可以接受,因此我接着找提高速度的方法。終於,在有關講解SQLite配置的資料中,看到了「寫同步」選項。c++

在SQLite中,數據庫配置的參數都由編譯指示(pragma)來實現的,而其中synchronous選項有三種可選狀態,分別是full、normal、off。這篇博客以及官方文檔裏面有詳細講到這三種參數的設置。簡要說來,full寫入速度最慢,但保證數據是安全的,不受斷電、系統崩潰等影響,而off能夠加速數據庫的一些操做,但若是系統崩潰或斷電,則數據庫可能會損毀。算法

SQLite3中,該選項的默認值就是full,若是咱們再插入數據前將其改成off,則會提升效率。若是僅僅將SQLite當作一種臨時數據庫的話,徹底不必設置爲full。在代碼中,設置方法就是在打開數據庫以後,直接插入如下語句:sql

 

[cpp]  view plain copy
 
 
  1. sqlite3_exec(db,"PRAGMA synchronous = OFF; ",0,0,0);  
此時,通過測試,插入速度已經變成了41851條/s,也就是說,插入100000000條數據,須要2389秒 = 39.8分。

 

5 極速——執行準備數據庫

雖然寫同步設爲off後,速度又有小幅提高,可是仍然較慢。我又一次踏上了尋找提升SQLite插入效率方法的道路上。終於,我發現,SQLite執行SQL語句的時候,有兩種方式:一種是使用前文提到的函數sqlite3_exec(),該函數直接調用包含SQL語句的字符串;另外一種方法就是「執行準備」(相似於存儲過程)操做,即先將SQL語句編譯好,而後再一步一步(或一行一行)地執行。若是採用前者的話,就算開起了事務,SQLite仍然要對循環中每一句SQL語句進行「詞法分析」和「語法分析」,這對於同時插入大量數據的操做來講,簡直就是浪費時間。所以,要進一步提升插入效率的話,就應該使用後者。安全

「執行準備」主要分爲三大步驟:數據結構

1.調用函數ide

 

[cpp]  view plain copy
 
 
  1. int sqlite3_prepare_v2( sqlite3 *db,  const char *zSql,  int nByte,  sqlite3_stmt **ppStmt,  const char **pzTail);  
而且聲明一個指向sqlite3_stmt對象的指針,該函數對參數化的SQL語句zSql進行編譯,將編譯後的狀態存入ppStmt中。

 

2.調用函數 sqlite3_step() ,這個函數就是執行一步(本例中就是插入一行),若是函數返回的是SQLite_ROW則說明仍在繼續執行,不然則說明已經執行完全部操做;

3.調用函數 sqlite3_finalize(),關閉語句。

關於執行準備的API的具體語法,詳見官方文檔。本文中執行準備的c++代碼以下:

 

[cpp]  view plain copy
 
 
  1. sqlite3_exec(db,"begin;",0,0,0);  
  2.     sqlite3_stmt *stmt;  
  3.     const char* sql = "insert into t1 values(?,?,?,?)";  
  4.     sqlite3_prepare_v2(db,sql,strlen(sql),&stmt,0);  
  5.       
  6.     for(int i=0;i<nCount;++i)  
  7.     {         
  8.         sqlite3_reset(stmt);  
  9.         sqlite3_bind_int(stmt,1,i);  
  10.         sqlite3_bind_int(stmt,1,i*2);  
  11.         sqlite3_bind_int(stmt,1,i/2);  
  12.         sqlite3_bind_double(stmt,1,i*i);  
  13.     }  
  14.     sqlite3_finalize(stmt);  
  15.     sqlite3_exec(db,"commit;",0,0,0);  
此時測試數據插入效率爲:265816條/s,也就是說,插入100000000條數據,須要376秒 = 6.27分。這個速度已經很滿意了。

 

5 總結

 

綜上所述啊,SQLite插入數據效率最快的方式就是:事務+關閉寫同步+執行準備(存儲過程),若是對數據庫安全性有要求的話,就開啓寫同步。

 

參考資料:

1. SQLite官方文檔: http://www.sqlite.org/docs.html
2.《解決sqlite3插入數據很慢的問題》: http://blog.csdn.net/victoryknight/article/details/7461703
3.《The Definitive Guide to SQLite》Apress出版: http://www.apress.com/9781430232254 (這是本好書)

 

附最終完整代碼:

 

 

[cpp]  view plain copy
 
 
  1. #include <iostream>  
  2. #include <string>  
  3. #include <sstream>  
  4. #include <time.h>  
  5. #include "sqlite3.h"  
  6.   
  7. const int nCount = 500000;  
  8.       
  9. int main (int argc,char** argv)  
  10. {  
  11.     sqlite3* db;  
  12.     sqlite3_open("testdb.db" ,&db);  
  13.     sqlite3_exec(db,"PRAGMA synchronous = OFF; ",0,0,0);  
  14.     sqlite3_exec(db,"drop table if exists t1",0,0,0);  
  15.     sqlite3_exec(db,"create table t1(id integer,x integer,y integer ,weight real)",0,0,0);  
  16.     clock_t t1 = clock();  
  17.       
  18.     sqlite3_exec(db,"begin;",0,0,0);  
  19.     sqlite3_stmt *stmt;  
  20.     const char* sql = "insert into t1 values(?,?,?,?)";  
  21.     sqlite3_prepare_v2(db,sql,strlen(sql),&stmt,0);  
  22.       
  23.     for(int i=0;i<nCount;++i)  
  24.     {  
  25.         // std::stringstream ssm;  
  26.         // ssm<<"insert into t1 values("<<i<<","<<i*2<<","<<i/2<<","<<i*i<<")";  
  27.         // sqlite3_exec(db,ssm.str().c_str(),0,0,0);  
  28.         sqlite3_reset(stmt);  
  29.         sqlite3_bind_int(stmt,1,i);  
  30.         sqlite3_bind_int(stmt,2,i*2);  
  31.         sqlite3_bind_int(stmt,3,i/2);  
  32.         sqlite3_bind_double(stmt,4,i*i);  
  33.         sqlite3_step(stmt);  
  34.     }  
  35.     sqlite3_finalize(stmt);  
  36.     sqlite3_exec(db,"commit;",0,0,0);  
  37.     clock_t t2 = clock();  
  38.       
  39.     sqlite3_close(db);  
  40.       
  41.     std::cout<<"cost tima: "<<(t2-t1)/1000.<<"s"<<std::endl;  
  42.       
  43.     return 0;  
  44. }  
相關文章
相關標籤/搜索