提升SQLite数据插入速度慢的方法

最近的文件搜索项目使用到了SQLite3作为数据库来缓存本地的文件,而通过USN读取到NTFS盘上的文件(开发机器上大概40w个文件/文件夹)在Intel Core i7-6700上大概就需要7s左右的时间,而通过常规的SQLite提供的C++ API 将文件的这些信息插入到数据库的表中,大概需要30s的时间,于是开始摸索是否有办法可以提高插入的效率进而减少我们程序的后台文件搜索耗时线程所花费的时间。

慢速:直接使用SQLite的C++ API

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

中速:显式开启事务

所谓”事务“就是指一组SQL命令,这些命令要么一起执行,要么都不被执行。在SQLite中,每调用一次sqlite3_exec()函数,就会隐式地开启了一个事务,如果插入一条数据,就调用该函数一次,事务就会被反复地开启、关闭,会增大IO量。如果在插入数据前显式开启事务,插入后再一起提交,则会大大提高IO效率,进而加数据快插入速度。

开启事务只需在上述代码的前后各加一句开启与提交事务的命令即可:

开启事务:

sqlite3_exec(db,"begin;",0,0,0);

提交事务:

sqlite3_exec(db,"commit;",0,0,0);

高速:关闭写同步(synchronous)

之前的速度仍然不能够接受,在有关讲解SQLite配置的资料中,看到了“写同步”选项。

在SQLite中,数据库配置的参数都由编译指示(pragma)来实现的,而其中synchronous选项有三种可选状态,分别是full、normal、off。这篇博客以及官方文档里面有详细讲到这三种参数的设置。简要说来,full写入速度最慢,但保证数据是安全的,不受断电、系统崩溃等影响,而off可以加速数据库的一些操作,但如果系统崩溃或断电,则数据库可能会损毁。

SQLite3中,该选项的默认值就是full,如果我们再插入数据前将其改为off,则会提高效率。如果仅仅将SQLite当做一种临时数据库的话,完全没必要设置为full。在代码中,设置方法就是在打开数据库之后,直接插入以下语句:

sqlite3_exec(db,"PRAGMA synchronous = OFF; ",0,0,0);

极速:

虽然写同步设为off后,速度又有小幅提升,但是仍然较慢。我又一次踏上了寻找提高SQLite插入效率方法的道路上。终于,我发现,SQLite执行SQL语句的时候,有两种方式:一种是使用前文提到的函数sqlite3_exec(),该函数直接调用包含SQL语句的字符串;另一种方法就是“执行准备”(类似于存储过程)操作,即先将SQL语句编译好,然后再一步一步(或一行一行)地执行。如果采用前者的话,就算开起了事务,SQLite仍然要对循环中每一句SQL语句进行“词法分析”和“语法分析”,这对于同时插入大量数据的操作来说,简直就是浪费时间。因此,要进一步提高插入效率的话,就应该使用后者。

“执行准备”主要分为三大步骤:

  1. 调用函数sqlite3_prepare_v2(),并且声明一个指向sqlite3_stmt对象的指针,该函数对参数化的SQL语句zSql进行编译,将编译后的状态存入ppStmt中。
  2. 调用函数 sqlite3_step(),这个函数就是执行一步(本例中就是插入一行),如果函数返回的是SQLite_ROW则说明仍在继续执行,否则则说明已经执行完所有操作。
  3. 调用函数 sqlite3_finalize(),关闭语句。
  4. 样例代码如下:
    int _tmain(int argc, _TCHAR* argv[])
    {
    	const int nCount = 500000;
    	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_t t1 = clock();
    
    	sqlite3_exec(db, "begin;", 0, 0, 0);
    	sqlite3_stmt *stmt;
    	const char* sql = "insert into t1 values(?,?,?,?)";
    	sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, 0);
    
    	for (int i = 0; i<nCount; ++i)
    	{
    		sqlite3_reset(stmt);
    		sqlite3_bind_int(stmt, 1, i);//bind第一个数据
    		sqlite3_bind_int(stmt, 2, i * 2);//bind第二个数据
    		sqlite3_bind_int(stmt, 3, i / 2);//bind第三个数据
    		sqlite3_bind_double(stmt, 4, i*i);//bind第四个数据
    		sqlite3_step(stmt);
    	}
    	sqlite3_finalize(stmt);
    	sqlite3_exec(db, "commit;", 0, 0, 0);
    	clock_t t2 = clock();
    	sqlite3_close(db);
    	std::cout << "cost time: " << (t2 - t1) / 1000. << "s" << std::endl;
    
    	system("pause");
    	return 0;
    }

     

综上所述啊,SQLite插入数据效率最快的方式就是:事务+关闭写同步+执行准备(存储过程),如果对数据库安全性有要求的话,就开启写同步。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注