ndfweb.cn

提升SQLite數據插入效率低、速度慢的方法


2021-04-24 09:48:25 (7977)



前言

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

 

1 數據

我使用的電腦是Win7 64位係統,使用VC2010編譯,SQLIte版本為3.7.15.2 ,電腦CPU為二代i3處理器,內存6G。

實驗之前,先建立要插入數據的表:

[sql] view plaincopy

 

 

  1. create table t1 (id integer , x integer , y integer, weight real)  

 

2 慢速——最粗暴的方法

SQLite的API中直接執行SQL的函數是:

[cpp] view plaincopy

 

 

  1. int sqlite3_exec(  sqlite3*,    const char *sql,   int (*callback)(void*,int,char**,char**),   void *,   char **errmsg)  

直接使用INSERT語句的字符串進行插入,程序部分代碼(完整代碼見後文),如下:

[cpp] view plaincopy

 

 

  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 plaincopy

 

 

  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倍。

4 高速——寫同步(synchronous)

我要使用一個遙感處理算法處理10000*10000的影像,中間有一步需要插入100000000條數據到數據庫中,如果按照開啟事務後的速度34095條/s,則需要100000000÷34095 = 2932秒 = 48.9分,仍然不能夠接受,所以我接著找提升速度的方法。終於,在有關講解SQLite配置的資料中,看到了“寫同步”選項。

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

SQLite3中,該選項的默認值就是full,如果我們再插入數據前將其改為off,則會提高效率。如果僅僅將SQLite當做一種臨時數據庫的話,完全沒必要設置為full。在代碼中,設置方法就是在打開數據庫之後,直接插入以下語句:

 

[cpp] view plaincopy

 

 

  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.調用函數

 

[cpp] view plaincopy

 

 

  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 plaincopy

 

 

  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 plaincopy

 

 

  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. }  

 


本文版权:http://www.ndfweb.cn/news-838.html
  NDF俱乐部
  国际域名注册
  建站咨询
简体中文 NDF网站建设淘宝店 | ICO图标在线生成 | 外贸网站建设 | 联系我们
©2007-2025 NDF Corporation 鲁ICP备08005967号 Sitemap - RSSRSS订阅