zoukankan      html  css  js  c++  java
  • 谈一谈sqlite这种小型数据库(1)

    SQLite是目前最流行的开源嵌入式数据库

    SQLite的主要特征:

       1. 管理简单,甚至可以认为无需管理。

       2. 操作方便,SQLite生成的数据库文件可以在各个平台无缝移植。

       3. 可以非常方便的以多种形式嵌入到其他应用程序中,如静态库、动态库等。

       4. 易于维护。

    综上所述,SQLite的主要优势在于灵巧、快速和可靠性高。

    由于SQLite在运行时占用的资源较少,而且无需任何管理开销,因此对于PDA、智能手机等 移动设备来说,SQLite的优势毋庸置疑。

    和RDBMS相比SQLite的一些劣势:

    1. C/S应用: 如果你有多个客户端需要同时访问数据库中的数据,特别是他们之间的数据操作是需要通过网 络传输来完成的。在这种情况下,不应该选择SQLite。由于SQLite的数据管理机制更多的依 赖于OS的文件系统,因此在这种操作下其效率较低。

    2. 数据量较大: 受限于操作系统的文件系统,在处理大数据量时,其效率较低。对于超大数据量的存储,甚至 不能提供支持。

    3. 高并发: 由于SQLite仅仅提供了粒度很粗的数据锁,如读写锁,因此在每次加锁操作中都会有大量的 数据被锁住,即使仅有极小部分的数据会被访问。换句话说,我们可以认为SQLite只是提供 了表级锁,没有提供行级锁。在这种同步机制下,并发性能很难高效。

    C/C++接口简介

    一、概述: 在SQLite提供的C/C++接口中,其中5个APIs属于核心接口。在这篇博客中我们将主 要介绍它们的用法,以及它们所涉及到的核心SQLite对象,如database_connection和 prepared_statement。相比于其它数据库引擎提供的APIs,如OCI、MySQL API等, SQLite提供的接口还是非常易于理解和掌握的。

    二、核心对象和接口:

    1. 核心对象:

      在SQLite中最主要的两个对象是,database_connection和prepared_statement。

    database_connection对象是由sqlite3_open()接口函数创建并返回的,在应用程序使用任何其 他SQLite接口函数之前,必须先调用该函数以便获得database_connnection对象,在随后的 其他APIs调用中,都需要该对象作为输入参数以完成相应的工作。至于prepare_statement, 我们可以简单的将它视为编译后的SQL语句,因此,所有和SQL语句执行相关的函数也都需要 该对象作为输入参数以完成指定的SQL操作。

    1). sqlite3_open

      上面已经提到过这个函数了,它是操作SQLite数据库的入口函数。该函数返回的 database_connection对象是很多其他SQLite APIs的句柄参数。注意,我们通过该函 数既可以打开已经存在的数据库文件,也可以创建新的数据库文件。对于该函数返回 的database_connection对象,我们可以在多个线程之间共享该对象的指针,以便完成 和数据库相关的任意操作。然而在多线程情况下,我们更为推荐的使用方式是,为每 个线程创建独立的database_connection对象。对于该函数还有一点也需要额外说明, 我们没有必要为了访问多个数据库而创建多个数据库连接对象,因为通过SQLite自带 的ATTACH命令可以在一个连接中方便的访问多个数据库。

      

    2). sqlite3_prepare

      该函数将SQL文本转换为prepared_statement对象,并在函数执行后返回该对象的指 针。事实上,该函数并不会评估参数指定SQL语句,它仅仅是将SQL文本初始化为待 执行的状态。最后需要指出的,对于新的应用程序我们可以使用sqlite3_prepare_v2接 口函数来替代该函数以完成相同的工作

    3). sqlite3_step

      该函数用于评估sqlite3_prepare函数返回的prepared_statement对象,在执行完该函 数之后,prepared_statement对象的内部指针将指向其返回的结果集的第一行。如果 打算进一步迭代其后的数据行,就需要不断的调用该函数,直到所有的数据行都遍历 完毕。然而对于INSERT、UPDATE和DELETE等DML语句,该函数执行一次即可完 成。

    4). sqlite3_column

      该函数用于获取当前行指定列的数据,然而严格意义上讲,此函数在SQLite的接口函 数中并不存在,而是由一组相关的接口函数来完成该功能,其中每个函数都返回不同 类型的数据

    如: sqlite3_column_blob  

       sqlite3_column_bytes  

      sqlite3_column_bytes16

      sqlite3_column_double

      sqlite3_column_int

      sqlite3_column_int64

       sqlite3_column_text

      sqlite3_column_text16

      sqlite3_column_type

       sqlite3_column_value

       sqlite3_column_count

       其中sqlite3_column_count函数用于获取当前结果集中的字段数据。下面是使用 sqlite3_step和sqlite3_column函数迭代结果集中每行数据的伪代码,注意这里作为示 例代码简化了对字段类型的判断:

      int fieldCount = sqlite3_column_count(...);

      while (sqlite3_step(...) <> EOF)

      {

         for (int i = 0; i < fieldCount; ++i)

         {

           int v = sqlite3_column_int(...,i);

         }

       }

    5). sqlite3_finalize

      该函数用于销毁prepared statement对象,否则将会造成内存泄露。

     6). sqlite3_close

       该函数用于关闭之前打开的database_connection对象,其中所有和该对象相关的 prepared_statements对象都必须在此之前先被销毁。

    三、参数绑定:

      和大多数关系型数据库一样,SQLite的SQL文本也支持变量绑定,以便减少SQL语句被动态 解析的次数,从而提高数据查询和数据操作的效率。要完成该操作,我们需要使用SQLite提 供的另外两个接口APIs,sqlite3_reset和sqlite3_bind。

     实例如下:

    void test_parameter_binding() {
     //1. 不带参数绑定的情况下插入多条数据。
     char strSQL[128];
     for (int i = 0; i < MAX_ROWS; ++i) {
      sprintf(strSQL,"insert into testtable values(%d)",i);
      sqlite3_prepare_v2(..., strSQL);
      sqlite3_step(prepared_stmt);
      sqlite3_finalize(prepared_stmt);
    }
    //2. 参数绑定的情况下插入多条数据。
    string strSQL = "insert into testtable values(?)";
      sqlite3_prepare_v2(..., strSQL);
    for (int i = 0; i < MAX_ROWS; ++i) {
      sqlite3_bind(...,i);
      sqlite3_step(prepared_stmt);
      sqlite3_reset(prepared_stmt);
    }
      sqlite3_finalize(prepared_stmt);
    }

    这里首先需要说明的是,SQL语句"insert into testtable values(?)"中的问号(?)表示参数变量的 占位符,该规则在很多关系型数据库中都是一致的,因此这对于数据库移植操作还是比较方便 的。

    通过上面的示例代码可以显而易见的看出,参数绑定写法的执行效率要高于每次生成不同的 SQL语句的写法,即2)在效率上要明显优于1),下面是针对这两种写法的具体比较:

    1. 单单从程序表面来看,前者在for循环中执行了更多的任务,比如字符串的填充、SQL语句 的prepare,以及prepared_statement对象的释放。

    2. 在SQLite的官方文档中明确的指出,sqlite3_prepare_v2的执行效率往往要低于 sqlite3_step的效率。

    3. 当插入的数据量较大时,后者带来的效率提升还是相当可观的。

    api 具体使用

      (1)在操作数据库之前,首先要打开数据库。这个函数打开一个sqlite数据库文件的连接并且返回一个数据库连接对象。这个操作同时程序中的第一个调用的sqlite函数,同时也是其他sqlite api的先决条件。许多的sqlite接口函数都需要一个数据库连接对象的指针作为它们的第一个参数。

    函数定义

    int sqlite3_open(

      const char *filename,   /* Database filename (UTF-8) */

      sqlite3 **ppDb          /* OUT: SQLite db handle */

    );

    假如这个要被打开的数据文件不存在,则一个同名的数据库文件将被创建。

    返回值:

    如果sqlite数据库被成功打开(或创建),将会返回SQLITE_OK,否则将会返回错误码。Sqlite3_errmsg()或者sqlite3_errmsg16可以用于获得数据库打开错误码的英文描述,这两个函数定义为:

      const char *sqlite3_errmsg(sqlite3*);

      const void *sqlite3_errmsg16(sqlite3*);

    参数说明:

    filename:需要被打开的数据库文件的文件名,在sqlite3_open和sqlite3_open_v2中这个参数采用UTF-8编码,而在sqlite3_open16中则采用UTF-16编码

    ppDb:一个数据库连接句柄被返回到这个参数,即使发生错误。唯一的一场是如果sqlite不能分配内存来存放sqlite对象,ppDb将会被返回一个NULL值

    (2)这个函数将sql文本转换成一个准备语句(prepared statement)对象,同时返回这个对象的指针。这个接口需要一个数据库连接指针以及一个要准备的包含SQL语句的文本。它实际上并不执行(evaluate)这个SQL语句,它仅仅为执行准备这个sql语句

    int sqlite3_prepare_v2(

      sqlite3 *db,            /* Database handle */

      const char *zSql,       /* SQL statement, UTF-8 encoded */

      int nByte,              /* Maximum length of zSql in bytes. */

      sqlite3_stmt **ppStmt,  /* OUT: Statement handle */

      const char **pzTail     /* OUT: Pointer to unused portion of zSql */

    );

    参数:

      db:数据指针

      zSql:sql语句,使用UTF-8编码

      nByte:如果nByte小于0,则函数取出zSql中从开始到第一个0终止符的内容;如果nByte不是负的,那么它就是这个函数能从zSql中读取的字节数的最大值。如果nBytes非负,zSql在第一次遇见’/000/或’u000’的时候终止

     

      ppStmt:能够使用sqlite3_step()执行的编译好的准备语句的指针,如果错误发生,它被置为NULL,如假如输入的文本不包括sql语句。调用过程必须负责在编译好的sql语句完成使用后使用sqlite3_finalize()删除它。

       pzTail:上面提到zSql在遇见终止符或者是达到设定的nByte之后结束,假如zSql还有剩余的内容,那么这些剩余的内容被存放到pZTail中,不包括终止符

    说明

      如果执行成功,则返回SQLITE_OK,否则返回一个错误码。推荐在现在任何的程序中都使用sqlite3_prepare_v2这个函数,sqlite3_prepare只是用于前向兼容

    备注

    <1>准备语句(prepared statement)对象

    typedef struct sqlite3_stmt sqlite3_stmt;

            

    准备语句(prepared statement)对象一个代表一个简单SQL语句对象的实例,这个对象通常被称为“准备语句”或者“编译好的SQL语句”或者就直接称为“语句”。

             语句对象的生命周期经历这样的过程:

      l  使用sqlite3_prepare_v2或相关的函数创建这个对象

      l  使用sqlite3_bind_*()给宿主参数(host parameters)绑定值

      l  通过调用sqlite3_step一次或多次来执行这个sql

      l  使用sqlite3——reset()重置这个语句,然后回到第2步,这个过程做0次或多次

      l  使用sqlite3_finalize()销毁这个对象

    在sqlite中并没有定义sqlite3_stmt这个结构的具体内容,它只是一个抽象类型,在使用过程中一般以它的指针进行操作,而sqlite3_stmt类型的指针在实际上是一个指向Vdbe的结构体得指针

    <2>宿主参数(host parameters)

      在传给sqlite3_prepare_v2()的sql的语句文本或者它的变量中,满足如下模板的文字将被替换成一个参数:

      l  ?

      l  ?NNN,NNN代表数字

      l  :VVV,VVV代表字符

      l  @VVV

      l  $VVV

    在上面这些模板中,NNN代表一个数字,VVV代表一个字母数字标记符(例如:222表示名称为222的标记符),sql语句中的参数(变量)通过上面的几个模板来指定,如

    “select ? from ? “这个语句中指定了两个参数,sqlite语句中的第一个参数的索引值是1,这就知道这个语句中的两个参数的索引分别为1和2,使用”?”的话会被自动给予索引值,而使用”?NNN”则可以自己指定参数的索引值,它表示这个参数的索引值为NNN。”:VVV”表示一个名为”VVV”的参数,它也有一个索引值,被自动指定。

    可以使用sqlite3_bind_*()来给这些参数绑定值

    3.  sqlite3_setp()

      这个过程用于执行有前面sqlite3_prepare创建的准备语句。这个语句执行到结果的第一行可用的位置。继续前进到结果的第二行的话,只需再次调用sqlite3_setp()。继续调用sqlite3_setp()知道这个语句完成,那些不返回结果的语句(如:INSERT,UPDATE,或DELETE),sqlite3_step()只执行一次就返回

    函数定义

      int sqlite3_step(sqlite3_stmt*);

    返回值

      函数的返回值基于创建sqlite3_stmt参数所使用的函数,假如是使用老版本的接口sqlite3_prepare()和sqlite3_prepare16(),返回值会  是 SQLITE_BUSY, SQLITE_DONE, SQLITE_ROW, SQLITE_ERROR 或 SQLITE_MISUSE,而v2版本的接口sqlite3_prepare_v2()和sqlite3_prepare16_v2()则会同时返回这些结果码和扩展结果码。

    对所有V3.6.23.1以及其前面的所有版本,需要在sqlite3_step()之后调用sqlite3_reset(),在后续的sqlite3_ step之前。如果调用sqlite3_reset重置准备语句失败,将会导致sqlite3_ step返回SQLITE_MISUSE,但是在V3. 6.23.1以后,sqlite3_step()将会自动调用sqlite3_reset。

      int sqlite3_reset(sqlite3_stmt *pStmt);

      sqlite3_reset用于重置一个准备语句对象到它的初始状态,然后准备被重新执行。所有sql语句变量使用sqlite3_bind*绑定值,使用sqlite3_clear_bindings重设这些绑定。Sqlite3_reset接口重置准备语句到它代码开始的时候。sqlite3_reset并不改变在准备语句上的任何绑定值,那么这里猜测,可能是语句在被执行的过程中发生了其他的改变,然后这个语句将它重置到绑定值的时候的那个状态。

      sqlite3_column()

    这个过程从执行sqlite3_step()执行一个准备语句得到的结果集的当前行中返回一个列。每次sqlite3_step得到一个结果集的列停下后,这个过程就可以被多次调用去查询这个行的各列的值。对列操作是有多个函数,均以sqlite3_column为前缀

    const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);

    int sqlite3_column_bytes(sqlite3_stmt*, int iCol);

    int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);

    double sqlite3_column_double(sqlite3_stmt*, int iCol);

    int sqlite3_column_int(sqlite3_stmt*, int iCol);

    sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);

    const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);

    const void *sqlite3_column_text16(sqlite3_stmt*, int iCol);

    int sqlite3_column_type(sqlite3_stmt*, int iCol);

    sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);

    说明

    第一个参数为从sqlite3_prepare返回来的prepared statement对象的指针,第二参数指定这一行中的想要被返回的列的索引。最左边的一列的索引号是0,行的列数可以使用sqlite3_colum_count()获得。

    int sqlite3_column_bytes(sqlite3_stmt*, int iCol)

    int sqlite3_column_bytes16(sqlite3_stmt*, int iCol)

    两个函数返回对应列的内容的字节数,这个字节数不包括后面类型转换过程中加上的0终止符。

    下面是几个最安全和最简单的使用策略

    • 先sqlite3_column_text() ,然后 sqlite3_column_bytes()
    • 先sqlite3_column_blob(),然后sqlite3_column_bytes()
    • 先sqlite3_column_text16(),然后sqlite3_column_bytes16()

      ============

        int sqlite3_finalize(sqlite3_stmt *pStmt);

    这个过程销毁前面被sqlite3_prepare创建的准备语句,每个准备语句都必须使用这个函数去销毁以防止内存泄露。

    在空指针上调用这个函数没有什么影响,同时可以准备语句的生命周期的任一时刻调用这个函数:在语句被执行前,一次或多次调用sqlite_reset之后,或者在sqlite3_step任何调用之后不管语句是否完成执行

      sqlite3_close

      这个过程关闭前面使用sqlite3_open打开的数据库连接,任何与这个连接相关的准备语句必须在调用这个关闭函数之前被释放

    实例如下

    一、获取表的Schema信息:

    1). 动态创建表。

    2). 根据sqlite3提供的API,获取表字段的信息,如字段数量以及每个字段的类型。

    3). 删除该表。

    #include <sqlite3.h>
    #include <string>
    using namespace std;
    void doTest() {
      sqlite3* conn = NULL;
      //1. 打开数据库
      int result = sqlite3_open(“./mytest.db",&conn);
      if (result != SQLITE_OK) {
        sqlite3_close(conn);
        return;
      }
      const char* createTableSQL ="CREATE TABLE TESTTABLE (int_col INT, float_col REAL, string_col TEXT)";
      sqlite3_stmt* stmt = NULL;
      int len = strlen(createTableSQL);
      //2. 准备创建数据表,如果创建失败,需要用sqlite3_finalize释放sqlite3_stmt对象,以防止内存泄露。
      if (sqlite3_prepare_v2(conn,createTableSQL,len,&stmt,NULL) != SQLITE_OK) {
      if (stmt)
        sqlite3_finalize(stmt);
      sqlite3_close(conn);
      return;
    }
    //3. 通过sqlite3_step命令执行创建表的语句。对于DDL和DML语句而言, sqlite3_step执行正确的返回值
    //只有SQLITE_DONE,对于SELECT查询而言,如果有数据返回SQLITE_ROW,当到达结果集末尾时则返回
    //SQLITE_DONE。
    if (sqlite3_step(stmt) != SQLITE_DONE) {
        sqlite3_finalize(stmt);
        sqlite3_close(conn);
        return;
    }
    //4. 释放创建表语句对象的资源。
    sqlite3_finalize(stmt);
    printf("Succeed to create test table now.
    ");
    //5. 构造查询表数据的sqlite3_stmt对象。
    const char* selectSQL = "SELECT * FROM TESTTABLE WHERE 1 = 0";
    sqlite3_stmt* stmt2 = NULL;
    if (sqlite3_prepare_v2(conn,selectSQL,strlen(selectSQL),&stmt2,NULL)!= SQLITE_OK) {
      if (stmt2)
        sqlite3_finalize(stmt2);
      sqlite3_close(conn);
      return;
    }
    //6. 根据select语句的对象,获取结果集中的字段数量。
      int fieldCount = sqlite3_column_count(stmt2);
      printf("The column count is %d.
    ",fieldCount);
    //7. 遍历结果集中每个字段meta信息,并获取其声明时的类型。
      for (int i = 0; i < fieldCount; ++i) {
    //由于此时Table中并不存在数据,再有就是SQLite中的数据类型本身是动态的,所以在没有数据时
    //无法通过sqlite3_column_type函数获取,此时sqlite3_column_type只会返回SQLITE_NULL,
    //直到有数据时才能返回具体的类型,因此这里使用了sqlite3_column_decltype函数来获取表声
    //明时给出的声明类型。
    string stype = sqlite3_column_decltype(stmt2,i);
    stype = strlwr((char*)stype.c_str());//strlwr()用于将字符串中的字符转换为小写,其原型为:char *strlwr(char *str)
    //下面的解析规则见该系列的“数据类型-->1. 决定字段亲缘性的规则”部分,
    if (stype.find("int") != string::npos) {
        printf("The type of %dth column is INTEGER.
    ",i);
    } else if (stype.find("char") != string::npos || stype.find("text") != string::npos) {
        printf("The type of %dth column is TEXT.
    ",i);
    } else if (stype.find("real") != string::npos || stype.find("floa") != string::npos    || stype.find("doub") != string::npos ) {
        printf("The type of %dth column is DOUBLE.
    ",i);
      }
    }
    sqlite3_finalize(stmt2);
    //8. 为了方便下一次测试运行,我们这里需要删除该函数创建的数据表,否则在下次运行时将无法
    //创建该表,因为它已经存在。
    const char* dropSQL = "DROP TABLE TESTTABLE";
    sqlite3_stmt* stmt3 = NULL;
    if (sqlite3_prepare_v2(conn,dropSQL,strlen(dropSQL),&stmt3,NULL) != SQLITE_OK)
    {
    if (stmt3)
      sqlite3_finalize(stmt3);
    sqlite3_close(conn);
    return;
    }
    if (sqlite3_step(stmt3) == SQLITE_DONE) {
      printf("The test table has been dropped.
    ");
    }
      sqlite3_finalize(stmt3);
      sqlite3_close(conn);
    }

    int main() {   doTest();   return 0; }

    运行结果:

    //输出结果为:

      //Succeed to create test table now

      //The column count is 3.

      //The type of 0th column is INTEGER.

       //The type of 1th column is DOUBLE.

      //The type of 2th column is TEXT.

      //The test table has been dropped.

    常规数据插入:

    1). 创建测试数据表。

    2). 通过INSERT语句插入测试数据。

    3). 删除测试表。

    #include <sqlite3.h>
    #include <string>
    #include <stdio.h>
    using namespace std;
    void doTest() {
    sqlite3* conn = NULL;
    //1. 打开数据库
    int result = sqlite3_open("./mytest.db",&conn);
    if (result != SQLITE_OK) {
      sqlite3_close(conn);
    return;
    }
    const char* createTableSQL ="CREATE TABLE TESTTABLE (int_col INT, float_col REAL, string_col TEXT)";
    sqlite3_stmt* stmt = NULL;
    int len = strlen(createTableSQL);
    //2. 准备创建数据表,如果创建失败,需要用sqlite3_finalize释放sqlite3_stmt对象,以防止内存泄露。
    if (sqlite3_prepare_v2(conn,createTableSQL,len,&stmt,NULL) != SQLITE_OK) {
      if (stmt)
        sqlite3_finalize(stmt);
      sqlite3_close(conn);
      return;
    }
    //3. 通过sqlite3_step命令执行创建表的语句。对于DDL和DML语句而言,sqlite3_step执行正确的返回值
    //只有SQLITE_DONE,对于SELECT查询而言,如果有数据返回SQLITE_ROW,当到达结果集末尾时则返回
    //SQLITE_DONE。
    if (sqlite3_step(stmt) != SQLITE_DONE) {
      sqlite3_finalize(stmt);
      sqlite3_close(conn);
      return;
    }
    //4. 释放创建表语句对象的资源。
    sqlite3_finalize(stmt);
    printf("Succeed to create test table now.
    ");
    int insertCount = 10;
    //5. 构建插入数据的sqlite3_stmt对象。
    const char* insertSQL = "INSERT INTO TESTTABLE VALUES(%d,%f,'%s')";
    const char* testString = "this is a test.";
    char sql[1024];
    sqlite3_stmt* stmt2 = NULL; for (int i = 0; i < insertCount; ++i) {     sprintf(sql,insertSQL,i,i * 1.0,testString); if (sqlite3_prepare_v2(conn,sql,strlen(sql),&stmt2,NULL) != SQLITE_OK) {     if (stmt2)       sqlite3_finalize(stmt2);   sqlite3_close(conn);   return; } if (sqlite3_step(stmt2) != SQLITE_DONE) {     sqlite3_finalize(stmt2);     sqlite3_close(conn);     return; } printf("Insert Succeed. "); } sqlite3_finalize(stmt2); //6. 为了方便下一次测试运行,我们这里需要删除该函数创建的数据表,否则在下次运行时将无法 //创建该表,因为它已经存在。 const char* dropSQL = "DROP TABLE TESTTABLE"; sqlite3_stmt* stmt3 = NULL; if (sqlite3_prepare_v2(conn,dropSQL,strlen(dropSQL),&stmt3,NULL) != SQLITE_OK) {   if (stmt3)     sqlite3_finalize(stmt3);   sqlite3_close(conn);   return; } if (sqlite3_step(stmt3) == SQLITE_DONE) {   printf("The test table has been dropped. "); } sqlite3_finalize(stmt3); sqlite3_close(conn); } int main() {   doTest();   return 0; }

    运行结果

     高效的批量数据插入: 在给出操作步骤之前先简单说明一下批量插入的概念,以帮助大家阅读其后的示例代码。事 实上,批量插入并不是什么新的概念,在其它关系型数据库的C接口API中都提供了一定的 支持,只是接口的实现方式不同而已。纵观众多流行的数据库接口,如OCI(Oracle API)、 MySQL API和PostgreSQL API等,OCI提供的编程接口最为方便,实现方式也最为高效。 SQLite作为一种简单灵活的嵌入式数据库也同样提供了该功能,但是实现方式并不像其他数 据库那样方便明显,它只是通过一种隐含的技巧来达到批量插入的目的,其逻辑如下:

    1). 开始一个事务,以保证后面的数据操作语句均在该事物内完成。在SQLite中,如果没 有手工开启一个事务,其所有的DML语句都是在自动提交模式下工作的,既每次操作后数 据均被自动提交并写入磁盘文件。然而在非自动提交模式下,只有当其所在的事物被手工 COMMIT之后才会将修改的数据写入到磁盘中,之前修改的数据都是仅仅驻留在内存中。显 而易见,这样的批量写入方式在效率上势必会远远优于多迭代式的单次写入操作。

    2). 基于变量绑定的方式准备待插入的数据,这样可以节省大量的sqlite3_prepare_v2函数调 用次数,从而节省了多次将同一SQL语句编译成SQLite内部识别的字节码所用的时间。事实 上,SQLite的官方文档中已经明确指出,在很多时候sqlite3_prepare_v2函数的执行时间要多 于sqlite3_step函数的执行时间,因此建议使用者要尽量避免重复调用sqlite3_prepare_v2函 数。在我们的实现中,如果想避免此类开销,只需将待插入的数据以变量的形式绑定到SQL语 句中,这样该SQL语句仅需调用sqlite3_prepare_v2函数编译一次即可,其后的操作只是替换 不同的变量数值。

    3). 在完成所有的数据插入后显式的提交事物。提交后,SQLite会将当前连接自动恢复为自动 提交模式。

    下面是示例代码的实现步骤:

    1). 创建测试数据表。

    2). 通过执行BEGIN TRANSACTION语句手工开启一个事物。

    3). 准备插入语句及相关的绑定变量。

    4). 迭代式插入数据。

    5). 完成后通过执行COMMIT语句提交事物。

    6). 删除测试表。

    #include <sqlite3.h>
    #include <string>
    #include <stdio.h>
    using namespace std;
    void doTest() {
      sqlite3* conn = NULL;
    //1. 打开数据库
      int result = sqlite3_open("./mytest.db",&conn);
    if (result != SQLITE_OK) {
        sqlite3_close(conn);
        return;
    }
    const char* createTableSQL ="CREATE TABLE TESTTABLE (int_col INT,float_col REAL, string_col TEXT)";
    sqlite3_stmt* stmt = NULL;
    int len = strlen(createTableSQL);
    //2. 准备创建数据表,如果创建失败,需要用sqlite3_finalize释放sqlite3_stmt对象以防止内存泄露。
    if (sqlite3_prepare_v2(conn,createTableSQL,len,&stmt,NULL) != SQLITE_OK) {
        if (stmt)
          sqlite3_finalize(stmt);
        sqlite3_close(conn);
        return;
    }
    //3. 通过sqlite3_step命令执行创建表的语句。对于DDL和DML语句而言,sqlite3_step执行正确的返回值
    //只有SQLITE_DONE,对于SELECT查询而言,如果有数据返回SQLITE_ROW,当到达结果集末尾时则返回
    //SQLITE_DONE。
    if (sqlite3_step(stmt) != SQLITE_DONE) {
        sqlite3_finalize(stmt);
        sqlite3_close(conn);
        return;
    }
    //4. 释放创建表语句对象的资源。
      sqlite3_finalize(stmt);
      printf("Succeed to create test table now.
    ");
    //5. 显式的开启一个事物。
    sqlite3_stmt* stmt2 = NULL;
    const char* beginSQL = "BEGIN TRANSACTION";
    if (sqlite3_prepare_v2(conn,beginSQL,strlen(beginSQL),&stmt2,NULL) !=SQLITE_OK) {
    if (stmt2)
        sqlite3_finalize(stmt2);
      sqlite3_close(conn);
      return;
    }
    if (sqlite3_step(stmt2) != SQLITE_DONE) {
      sqlite3_finalize(stmt2);
      sqlite3_close(conn);
      return;
    }
      sqlite3_finalize(stmt2);
    //6. 构建基于绑定变量的插入数据。
    const char* insertSQL = "INSERT INTO TESTTABLE VALUES(?,?,?)";
    sqlite3_stmt* stmt3 = NULL;
    if (sqlite3_prepare_v2(conn,insertSQL,strlen(insertSQL),&stmt3,NULL) !=SQLITE_OK) {
      if (stmt3)
        sqlite3_finalize(stmt3);
      sqlite3_close(conn);
      return;
      }
    int insertCount = 10;
    const char* strData = "This is a test.";
    //7. 基于已有的SQL语句,迭代的绑定不同的变量数据
    for (int i = 0; i < insertCount; ++i) {
    //在绑定时,最左面的变量索引值是1。
    sqlite3_bind_int(stmt3,1,i);
    sqlite3_bind_double(stmt3,2,i * 1.0);
    sqlite3_bind_text(stmt3,3,strData,strlen(strData),SQLITE_TRANSIENT);
    if (sqlite3_step(stmt3) != SQLITE_DONE) {
        sqlite3_finalize(stmt3);
        sqlite3_close(conn);
      return;
    }
    //重新初始化该sqlite3_stmt对象绑定的变量。
    sqlite3_reset(stmt3);
    printf("Insert Succeed.
    ");
    }
    sqlite3_finalize(stmt3);
    //8. 提交之前的事物。
    const char* commitSQL = "COMMIT";
    sqlite3_stmt* stmt4 = NULL;
    if (sqlite3_prepare_v2(conn,commitSQL,strlen(commitSQL),&stmt4,NULL) !=SQLITE_OK) {
      if (stmt4)
      sqlite3_finalize(stmt4);
     sqlite3_close(conn);
     return;
    }
    if (sqlite3_step(stmt4) != SQLITE_DONE) {
      sqlite3_finalize(stmt4);
      sqlite3_close(conn);
      return;
    }
    sqlite3_finalize(stmt4);
    //9. 为了方便下一次测试运行,我们这里需要删除该函数创建的数据表,否则在下次运行时将无法
    //创建该表,因为它已经存在。
    const char* dropSQL = "DROP TABLE TESTTABLE";
    sqlite3_stmt* stmt5 = NULL;
    if (sqlite3_prepare_v2(conn,dropSQL,strlen(dropSQL),&stmt5,NULL) != SQLITE_OK)
    {
      if (stmt5)
        sqlite3_finalize(stmt5);
      sqlite3_close(conn);
      return;
    }
    if (sqlite3_step(stmt5) == SQLITE_DONE) {
    printf("The test table has been dropped.
    ");
    }
    sqlite3_finalize(stmt5);
    sqlite3_close(conn);
    }
    int main() {
        doTest();
        return 0;          
    }
    

      

    数据查询: 数据查询是每个关系型数据库都会提供的最基本功能,下面的代码示例将给出如何通过 SQLite API获取数据。

    1). 创建测试数据表。

    2). 插入一条测试数据到该数据表以便于后面的查询。

    3). 执行SELECT语句检索数据。

    4). 删除测试表。

    #include <sqlite3.h>
    #include <string>
    #include <stdio.h>
    using namespace std;
    void doTest() {
    sqlite3* conn = NULL;
    //1. 打开数据库
    int result = sqlite3_open("D:/mytest.db",&conn);
    if (result != SQLITE_OK) {
    sqlite3_close(conn);
    return;
    }
    const char* createTableSQL ="CREATE TABLE TESTTABLE (int_col INT, float_col REAL, string_col TEXT)";
    sqlite3_stmt* stmt = NULL;
    int len = strlen(createTableSQL);
    //2. 准备创建数据表,如果创建失败,需要用sqlite3_finalize释放sqlite3_stmt对象,以防止内存泄露。
    if (sqlite3_prepare_v2(conn,createTableSQL,len,&stmt,NULL) != SQLITE_OK) {
    if (stmt)
    sqlite3_finalize(stmt);
    sqlite3_close(conn);
    return;
    }
    //3. 通过sqlite3_step命令执行创建表的语句。对于DDL和DML语句而言,sqlite3_step执行正确的返回值
    //只有SQLITE_DONE,对于SELECT查询而言,如果有数据返回SQLITE_ROW,当到达结果集末尾时则返回
    //SQLITE_DONE。
    if (sqlite3_step(stmt) != SQLITE_DONE) {
    sqlite3_finalize(stmt);
    sqlite3_close(conn);
    return;
    }
    //4. 释放创建表语句对象的资源。
    sqlite3_finalize(stmt);
    printf("Succeed to create test table now.
    ");
    //5. 为后面的查询操作插入测试数据。
    sqlite3_stmt* stmt2 = NULL;
    const char* insertSQL = "INSERT INTO TESTTABLE VALUES(20,21.0,'this is a test.')";
    if (sqlite3_prepare_v2(conn,insertSQL,strlen(insertSQL),&stmt2,NULL) !=SQLITE_OK) {
    if (stmt2)
    sqlite3_finalize(stmt2);
    sqlite3_close(conn);
    return;
    }
    if (sqlite3_step(stmt2) != SQLITE_DONE) {
    sqlite3_finalize(stmt2);
    sqlite3_close(conn);
    return;
    }
    printf("Succeed to insert test data.
    ");
    sqlite3_finalize(stmt2);
    //6. 执行SELECT语句查询数据。
    const char* selectSQL = "SELECT * FROM TESTTABLE";
    sqlite3_stmt* stmt3 = NULL;
    if (sqlite3_prepare_v2(conn,selectSQL,strlen(selectSQL),&stmt3,NULL) !=SQLITE_OK) {
    if (stmt3)
    sqlite3_finalize(stmt3);
    sqlite3_close(conn);
    return;
    }
    int fieldCount = sqlite3_column_count(stmt3);
    do {
    int r = sqlite3_step(stmt3);
    if (r == SQLITE_ROW) {
    for (int i = 0; i < fieldCount; ++i) {
    //这里需要先判断当前记录当前字段的类型,再根据返回的类型使用不同的API函数
    //获取实际的数据值。
    int vtype = sqlite3_column_type(stmt3,i);
    if (vtype == SQLITE_INTEGER) {

    int v = sqlite3_column_int(stmt3,i);   printf("The INTEGER value is %d. ",v); } else if (vtype == SQLITE_FLOAT) {   double v = sqlite3_column_double(stmt3,i);   printf("The DOUBLE value is %f. ",v); } else if (vtype == SQLITE_TEXT) {   const char* v = (const char*)sqlite3_column_text(stmt3,i);     printf("The TEXT value is %s. ",v); } else if (vtype == SQLITE_NULL) {   printf("This value is NULL. "); } } } else if (r == SQLITE_DONE) { printf("Select Finished. "); break; } else { printf("Failed to SELECT. "); sqlite3_finalize(stmt3); sqlite3_close(conn); return; } } while (true); sqlite3_finalize(stmt3); //7. 为了方便下一次测试运行,我们这里需要删除该函数创建的数据表,否则在下次运行时将无法 //创建该表,因为它已经存在。 const char* dropSQL = "DROP TABLE TESTTABLE"; sqlite3_stmt* stmt4 = NULL; if (sqlite3_prepare_v2(conn,dropSQL,strlen(dropSQL),&stmt4,NULL) != SQLITE_OK) { if (stmt4) sqlite3_finalize(stmt4); sqlite3_close(conn); return; } if (sqlite3_step(stmt4) == SQLITE_DONE) { printf("The test table has been dropped. "); } sqlite3_finalize(stmt4); sqlite3_close(conn); } int main() { doTest(); return 0; }

    } //输出结果如下:

    //Succeed to create test table now.

    //Succeed to insert test data.

    //The INTEGER value is 20.

    //The DOUBLE value is 21.000000.

    //The TEXT value is this is a test.

    .//Select Finished.

    //The test table has been dropped.

    更简单一点的用法:

     

    回调函数

    int sqlite_callback( 
      void* pvoid, /* 由 sqlite3_exec() 的第四个参数传递而来 */ 
      int argc, /* 表的列数或者记录中包含的字段数目 */ 
      char** argv, /* 指向查询结果的指针数组, 可以由 sqlite3_column_text() 得到 ,包含每个字段值的指针数组*/ 
      char** col /* 指向表头名的指针数组, 可以由 sqlite3_column_name() 得到,包含每个字段名称的指针数组 */ 
    ); 
    在所有的回掉函数中,都要加上return 0;否则失去其回调函数的意义。

    注意:其实是sqlite3_exec在查询表的时候,每查到一条记录,就会调用一次回调函数,所以才是会显示出所有数据

    int sqlite3_get_table(
      sqlite3 *db,          /* An open database */
      const char *zSql,     /* SQL to be evaluated */
      char ***dbResult,    /* Results of the query */
      int *pnRow,           /* Number of result rows written here */
      int *pnColumn,        /* Number of result columns written here */
      char **pzErrmsg       /* Error msg written here */
    );
    void sqlite3_free_table(char **result);

    第1个参数不再多说,看前面的例子。
    第2个参数是sql 语句,跟sqlite3_exec 里的sql 是一样的。是一个很普通的以结尾的char*字符串。
    第3个参数是查询结果,它依然一维数组(不要以为是二维数组,更不要以为是三维数组)。它内存布局是:字段名称,后面是紧接着是每个字段的值。下面用例子来说事。
    第4个参数是查询出多少条记录(即查出多少行,不包括字段名那行)。
    第5个参数是多少个字段(多少列)。
    第6个参数是错误信息,跟前面一样,这里不多说了。

    pazResult返回的字符串数量实际上是(*pnRow+1)*(*pnColumn),因为前(*pnColumn)个是字段名

    {
     sqlite3 *db;
     char *errmsg=NULL;    //用来存储错误信息字符串
     char ret=0;
     int my_age=0;    //类型根据要提取的数据类型而定
     char **dbResult; 
     int nRow=0, nColumn=0;     //nRow 查找出的总行数,nColumn 存储列
    
     ret = sqlite3_open("student.db",&db);
     if(1 == ret)     //数据库创建未成功
     {
      fprintf(stderr, "Can't open this database: %s
    ", sqlite3_errmsg(db));    //用sqlite3_errmsg()得到错误字符串
      sqlite3_close(db);
      return -1;
     }
    
     ret=sqlite3_get_table(db, "select * from age;", &dbResult, &nRow, &nColumn, &errmsg);
    
     if(NULL!=errmsg)
     {
      sqlite3_free_table(dbResult);
      errmsg=NULL;
      return -1
     }
    
     my_age = atoi(dbResult[nColumn]);
     sqlite3_free_table(dbResult);
     return 0;
    }


    注意:

    sqlite3 *db创建数据库类型的指针,通过sqlite3_open()函数使db指针指向该数据库。
    注意:
    1、 char **dbResult; 字符型的二重指针,将数据库里sqlite3_get_table()出来的数据以字符的方式给dbResult。
    2、select * from age;查询student数据库里的age表全部内容。
    3、my_age = atoi(dbResult[nColumn]);将查询出来给dbResult的数据(字符)通过aoti()转换成整型交给变量my_age供程序中直接应用。
    重点:(假设age表里有n个字段)
    1、通过select * from age;给dbResult的字符前n个(0,n)为字段名称(只有计算机认识),dbResult[n]以后分别代表字段的值(包括dbResult[n])。如图
               *      *      *      * .........*  (dbResult[0]~[n-1]分别代表字段名)
      dbResult[n]   [n+1]  [n+2]  [n+3].....[n+n-1] (dbResult[n]~[n+n-1]分别代表第一条记录的值)
      dbResult[2n]  [2n+1] [2n+2] [2n+3]....[2n+n-1](dbResult[2n]~[2n+n-1]分别代表第二条记录的值)
      dbResult[3n]  [3n+1] [3n+2] 32n+3]....[3n+n-1](dbResult[3n]~[3n+n-1]分别代表第三条记录的值)
    注:sqlite3_get_table()之后便将以上的n(字段数:简称列)给了nColumn这个变量,可直接应用。nRow变量代表共有多少条记录,可直接应用。
    2、通过select * from age where id=0;如果查到0条记录的话nRow等于0,查到1条记录的话nRow等于1,假设查到1条数据,举例:
               *      *      *      * .........*  (dbResult[0]~[n-1]分别代表字段名)
      dbResult[n]   [n+1]  [n+2]  [n+3].....[n+n-1] (dbResult[n]~[n+n-1]分别代表第一条记录的值)
    注:此时dbResult[]只有0~2n-1共2n个字符,此时如果对dbResult[2n]引用的话就会出错。查询两条语句的话以此类推。
               *      *      *      * .........*  (dbResult[0]~[n-1]分别代表字段名)
      dbResult[n]   [n+1]  [n+2]  [n+3].....[n+n-1] (dbResult[n]~[n+n-1]分别代表第一条记录的值)
      dbResult[2n]  [2n+1] [2n+2] [2n+3]....[2n+n-1](dbResult[2n]~[2n+n-1]分别代表第二条记录的值)
    注:此时dbResult[]只有0~3n-1可引用。
    

      具体实例:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sqlite3.h>
    
    int create_table(sqlite3 *db)
    {
        char *errmsg = NULL;
        char *sql;
    
        sql = "create table if not exists mytable(id integer primary key,name text);";
    
        if(SQLITE_OK != sqlite3_exec(db,sql,NULL,NULL,&errmsg))
        {
            printf("operater failed1 : %s 
    ",errmsg);
            exit(0);
        }
    }
    
    int insert_record(sqlite3 *db)
    {
        char *errmsg;
        char sql[100];
        char name[10];
        int id;
        int n;
        int i;
        printf("enter the number 
    ");
        scanf("%d",&n);
        for(i = 0; i < n;i++)
        {
            printf("enter the id you want to insert :
    ");
            scanf("%d",&id);
            printf("enter the name you want to insert :
    ");
            scanf("%s",name);
    
            sprintf(sql,"insert into mytable(id,name) values(%d,'%s');",id,name);
            if(SQLITE_OK != sqlite3_exec(db,sql,NULL,NULL,&errmsg))
            {
                printf("operater failed2 : %s 
    ",errmsg);
                exit(0);
            }
        }
    
    }
    
    int displaycb(void *para,int n_column,char **column_value,char **column_name)
    {
        int i;
        printf("total column is %d 
    ",n_column);
        for(i = 0;i < n_column;i++)
        {
            //      printf("		 %s 		 %s 
    ",column_name[i],column_value[i]);
            printf("column_name : %s ----> column_value : %s 
    ",column_name[i],column_value[i]);
        }
        return 0;
    }
    
    int inquire_usecb(sqlite3 *db)
    {
        char *errmsg;
        char *sql;
    
        sql = "select * from mytable;";
    
        if(SQLITE_OK != sqlite3_exec(db,sql,displaycb,NULL,&errmsg))
        {
            printf("operater failed5 : %s 
    ",errmsg);
            exit(0);
        }
    }
    int delete_record(sqlite3 *db)
    {
        char *errmsg;
        char sql[100];
        int id;
    
        printf("enter the id you want to delete
    ");
        scanf("%d",&id);
    
        sprintf(sql,"delete from mytable where id = %d",id);
    
        if(SQLITE_OK != sqlite3_exec(db,sql,NULL,NULL,&errmsg))
        {
            printf("operater failed6 :%s 
    ",errmsg);
            exit(0);
        }
    }
    
    int inquire_nocb(sqlite3 *db)
    {
        int nrow,ncolumn;
        char **azresult;
        char *sql;
        char *errmsg;
        int i;
        sql = "select * from mytable;";
        if(SQLITE_OK != sqlite3_get_table(db,sql,&azresult,&nrow,&ncolumn,&errmsg))
        {
            printf("operater failed : %s
    ",errmsg);
            exit(0);
        }
        printf("row :%d        column :%d
    ",nrow,ncolumn);
        printf("the result of querying : 
    ");
        for(i = 0;i < (nrow + 1) * ncolumn;i++)
        {
            printf("%10s",azresult[i]);
            if((i + 1) % ncolumn == 0)
            {
                printf("
    ");
            }
        }
        sqlite3_free_table(azresult);
    }
    int main()
    {
        sqlite3 *db = NULL;
        int ret;
    
        ret = sqlite3_open("mydatabase.db",&db);
    
        if(ret != SQLITE_OK)
        {
            perror("open error!
    ");
            exit(0);
        }
        else
        {
            printf("you have opened a qulite3 database successfully !
    ");
        }
    
        create_table(db);
        insert_record(db);
        inquire_usecb(db);
        delete_record(db); 
        inquire_nocb(db);
        sqlite3_close(db);
        return 0;
    }

    运行结果:

  • 相关阅读:
    docker创建tomcat容器
    【转载】张一鸣:为什么 BAT 挖不走我们的人才?
    Elastic认证考试,请先看这一篇
    vs code 初始化vue项目框架
    Idea集成git常用命令
    pxc搭建mysql集群
    mysql无限级分类
    Java面试题大全
    SpringMVC和Spring
    Redis高级特性
  • 原文地址:https://www.cnblogs.com/bwbfight/p/9306293.html
Copyright © 2011-2022 走看看