转载地址:http://blog.csdn.net/chenguanzhou123/article/details/9376537#,如果有侵犯原创,请留言告知,本人会及时删除。

前言

SQLite数据库由于其简单、灵活、轻量、开源,已经被越来越多的被应用到中小型应用中。甚至有人说,SQLite完全可以用来取代c语言中的文件读写操作。因此我最近编写有关遥感数据处理的程序的时候,也将SQLite引入进来,以提高数据的结构化程度,并且提高大数据的处理能力(SQLite最高支持2PB大小的数据)。但是最开始,我发现,直接使用SQL语句的插入效率简直低的令人发指的。后来不断查文档、查资料,才发现了一条快速的“数据插入”之路。本文就以插入数据为例,整合网上和资料书中的各种提高SQLite效率的方法,给出提高SQLite数据插入效率的完整方法。(大神们勿喷)

1 数据

我使用的电脑是Win7 64位系统,使用VC2010编译,SQLIte版本为3.7.15.2 ,电脑CPU为二代i3处理器,内存6G。
实验之前,先建立要插入数据的表:
  1. create table t1 (id integer , x integer , y integer, weight real)

2 慢速——最粗暴的方法

SQLite的API中直接执行SQL的函数是:

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

直接使用INSERT语句的字符串进行插入,程序部分代码(完整代码见后文),如下:

  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效率,进而加数据快插入速度。
开启事务只需在上述代码的前后各加一句开启与提交事务的命令即可:
  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。在代码中,设置方法就是在打开数据库之后,直接插入以下语句:

  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.调用函数

  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++代码如下:

  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. for(int i=0;i<nCount;++i)
  6. {
  7. sqlite3_reset(stmt);
  8. sqlite3_bind_int(stmt,1,i);
  9. sqlite3_bind_int(stmt,1,i*2);
  10. sqlite3_bind_int(stmt,1,i/2);
  11. sqlite3_bind_double(stmt,1,i*i);
  12. }
  13. sqlite3_finalize(stmt);
  14. sqlite3_exec(db,"commit;",0,0,0);

此时测试数据插入效率为:265816条/s,也就是说,插入100000000条数据,需要376秒 = 6.27分。这个速度已经很满意了。

6 总结

综上所述啊,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 (这是本好书)

附最终完整代码:

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

[转载]提升SQLite数据插入效率低、速度慢的方法的更多相关文章

  1. 提升SQLite数据插入效率低、速度慢的方法

    前言 SQLite数据库由于其简单.灵活.轻量.开源,已经被越来越多的被应用到中小型应用中.甚至有人说,SQLite完全可以用来取代c语言中的文件读写操作.因此我最近编写有关遥感数据处理的程序的时候, ...

  2. 提升SQLite数据插入效率低、速度慢的方法(转)

    前言 SQLite数据库由于其简单.灵活.轻量.开源,已经被越来越多的被应用到中小型应用中.甚至有人说,SQLite完全可以用来取代C语言中的文件读写操作.因此我最近编写有关遥感数据处理的程序的时候, ...

  3. 提升R代码运算效率的11个实用方法

    提升R代码运算效率的11个实用方法 众所周知,当我们利用R语言处理大型数据集时,for 循环语句的运算效率非常低.有许多种方法可以提升你的代码运算效率,但或许你更想了解运算效率能得到多大的提升.本文将 ...

  4. MFC使用SQLite 学习系列 二:无法容忍的数据插入效率

    上一篇随笔中,介绍了,基本的使用没什么问题了,那么开始数据的插入. 一 问题--无法容忍的插入效率 代码写入基本完成,然后开始测试.起初,插入数据的时候基本上是插入每次插入9组数据,看不出来数据插入的 ...

  5. 提升R代码运算效率的11个实用方法——并行、效率

    转载于36大数据,原文作者:Selva Prabhakaran  译者:fibears 众所周知,当我们利用R语言处理大型数据集时,for循环语句的运算效率非常低.有许多种方法可以提升你的代码运算效率 ...

  6. 【R】提升R代码运算效率的11个实用方法

    低.有许多种方法可以提升你的代码运算效率,但或许你更想了解运算效率能得到多大的提升.本文将介绍几种适用于大数据领域的方法,包括简单的逻辑调整设计.并行处理和Rcpp的运用,利用这些方法你可以轻松地处理 ...

  7. 解决 C++ 操作 MySQL 大量数据插入效率低下问题

    往 Mysql 中,插入10000条简单数据.速度很缓慢,竟然要5分钟左右, 可是打开事务的话.一秒不到就搞定了 代码: #include <iostream> #include < ...

  8. C++操作MySQL大量数据插入效率低下的解决方法

    #include <iostream> #include <winsock2.h> #include <string> #include "mysql.h ...

  9. android sqlite批量插入数据速度解决方案

    转自 http://hi.baidu.com/hfutonline/blog/item/62b1e4de8bdf4b2e5882dd28.html 最近在做android项目的时候遇到一个问题,应用程 ...

随机推荐

  1. NOIP 模拟 $34\; \rm Rectangle$

    题解 \(by\;zj\varphi\) 对于没有在同一行或同一列的情况,直接枚举右边界,左边界从大到小,用树状数组维护上下边界即可. 而对于有多个在一列或一行的情况,这些点将左右分成了几个区间,枚举 ...

  2. 题解 Smooth

    传送门 咕了蚯蚓这题+前一天被蚊子折腾了半宿没睡=全场几乎就我这题分最低-- 发现这里光滑数是不断乘出来的--记得这是一个很经典的套路,然而当时我咕了 求一个每个值都是 \(\{p_i\}\) 中至少 ...

  3. 8、二进制安装K8s之部署CIN网络

    二进制安装K8s之部署CIN网络 部署CIN网络可以使用flannel或者calico,这里介绍使用calico ecd 方式部署. 1.下载calico二进制安装包 创建所需目录 mkdir -p ...

  4. 【转】 C#中检查网络是否连通的二种方法

      1 using System;  2 using System.Collections.Generic;  3 using System.Text;  4 //方法一  5 using Syste ...

  5. Socket编程 Tcp和粘包

    大多数程序员都要接触网络编程,Web开发天天和http打交道.稍微底层一点的程序员,就是TCP/UDP . 对程序员来说,Tcp/udp的核心是Socket编程. 我的浅薄的观点---------理解 ...

  6. git cherry-pick 教程

    转自:http://www.ruanyifeng.com/blog/2020/04/git-cherry-pick.html 对于多分支的代码库,将代码从一个分支转移到另一个分支是常见需求. 这时分两 ...

  7. 实现Comparable接口

    1 import java.util.TreeSet; 2 3 4 /** 5 * PriorityQueue, TreeSet是排序集合,存储的对象必须实现Comparable接口. 6 * 原因是 ...

  8. C# 简单粗暴的毫秒转换成 分秒的格式

    C# 简单粗暴的毫秒转换成 分秒的格式 1:code(网络上很多存在拷贝或者存在bug的或者不满足自己的要求) 1 public static string RevertToTime(double m ...

  9. centos7 netstat

    netstat 是控制台命令,它可以显示路由表.实际的网络连接以及每一个网络接口设备的状态信息.Netstat 用于显示与 IP . TCP . UDP 和 ICMP 协议相关的统计数据,一般用于检验 ...

  10. vue 上传头像悬浮显示文字

    template部分: 头像外部加一个 div <div class="user-info-head"> </div>   css 部分 <style ...