閱讀910 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Android數據庫代碼優化(2) - 從SQLite說起

從SQLite說起

如果沒有SQLite的基礎,我們隻是從Android封裝的SQLite API去學習的話,難免思路會受到限製。所以,我們還是需要老老實實從頭開始學習SQLite.
當我們有一身的SQLite武功之後,再去看Android的封裝,就能更清楚如何發揮SQLite的特長。

SQLite的核心隻有一個c文件,訪問的db也存在一個文件當中。所以,我們完全可以把它嵌入到另外一個程序中。

在mac上,可以通過Homebrew來安裝。安裝之後,我們就可以用sqlite3的API來寫代碼了。

先來個能編過的sqlite3調用例子吧

我們找個網上找到的最簡單的打開關閉SQLite數據庫的例子:

#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    sqlite3 *db;
    char *zErrMsg = 0;
    int rc;

    rc = sqlite3_open("contacts.db", &db);

    if (rc)
    {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        exit(0);
    }
    else
    {
        fprintf(stderr, "Opened database successfully\n");
    }
    sqlite3_close(db);
}

我們先不管它是什麼意思,先編譯一下試試:

gcc -o test_sqlite test.c -lsqlite3

然後運行一下吧,需要本地有個叫contacts.db的數據庫。

./test_sqlite

輸出為:

Opened database successfully

從上麵的例子,我們可以學習到兩個容易理解的API: sqlite3_open和sqlite3_close.

SQLite3是一個基於VDBE的數據庫引擎

有了能運行的環境之後,我們就來看看SQLite數據庫引擎的結構吧:

sqlite3

從這張官方圖上,我們可以看到,除了工具和測試代碼之外,SQLite的核心部分分為三部分:核心,編譯器和後端。

核心部分就是對SQL命令的處理的部分,它通過編譯器來編譯成VDBE(Virtual Database Engine)能執行的代碼。
後端是真正對數據庫進行操作的部分,包括B-樹的查找結構等。

喜歡劃重點的同學注意啦,重點來了:調用SQLite3數據庫的代碼優化的第一個點就是將編譯好的字節碼保存起來,下次用的時候直接調用。
這麼重要的功能,SQLite3 API中當然有提供,這就是後麵我們會大量學習使用的sqlite3_prepare和sqlite3_prepare_v2函數。
Android對此也有同樣的封裝,提供了SQLiteStatement來實現預編譯代碼的保存。

有同學問了,我的SQL語句並不是一成不變的,語句中的參數經常改變,這樣的話,編譯出來的代碼就沒有用了啊?
這在SQLite3的設計中當然是有考慮到的,編譯好的語句,是可以支持參數的。我們首先使用sqlite3_prepare_v2編譯,然後再通過sqlite3_bind_*函數來綁定參數。下次如果換了參數,先調用sqlite3_reset清除掉綁定信息,然後再重新用sqlite3_bind_*來做綁定新參數,就可以了。

一個調用sqlite3實現數據庫操作的功能可以用下麵的步驟來套用:
1. 根據業務需求,構造sql語句
2. 調用sqlite3_prepare_v2函數來編譯sql語句
3. 如果有參數,調用sqlite3_bind_*函數來綁定參數
4. 調用sqlite3_step函數來執行一次sql操作,直至所有操作都完成
5. 下次再使用第2步編譯出來的語句時,調用sqlite3_reset函數清理參數。然後重複第3步的操作
6. 最後,調用sqlite3_finalize來銷毀預編譯語句

下麵我們直接開始實操,首先先舉個select的例子。
我們以Android中聯係人數據庫為例,取其中的calls表的簡化版:

CREATE TABLE calls (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
sourceid TEXT,
number TEXT
...
);

雖然字段很多,我們就關注id和號碼就好。

如何寫查詢語句

我們先來一半,我們選_id和number這兩列,然後看看返回的數據中是不是兩列:
核心代碼如下:

    rc = sqlite3_prepare_v2(db, sql_select_caller, -1, &stmt, &tail);

    rc = sqlite3_step(stmt);
    int ncols = sqlite3_column_count(stmt);

    printf("The column counts of calls is:%d\n", ncols);

    sqlite3_finalize(stmt);

完整版的代碼,便於大家實驗:

#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    sqlite3 *db;
    char *zErrMsg = 0;
    int rc;
    const char *sql_select_caller = "select _id, number from calls";
    sqlite3_stmt *stmt;
    const char *tail;

    rc = sqlite3_open("contacts.db", &db);

    if (rc)
    {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        exit(0);
    }
    else
    {
        fprintf(stderr, "Opened database successfully\n");
    }

    //select _id, number from calls
    rc = sqlite3_prepare_v2(db, sql_select_caller, -1, &stmt, &tail);

    rc = sqlite3_step(stmt);
    int ncols = sqlite3_column_count(stmt);

    printf("The column counts of calls is:%d\n", ncols);

    sqlite3_finalize(stmt);

    sqlite3_close(db);
}

下麵我們直接調用sqlite3_step去讀每一條記錄,增加下麵一段:

    while(rc == SQLITE_ROW){
        printf("calls ID=%d,\t",sqlite3_column_int(stmt,0));
        printf("number=%s\n",sqlite3_column_text(stmt,1));
        rc = sqlite3_step(stmt);
    }

如果sqlite3_step返回的結果是SQLITE_ROW,說明這一次執行取到了一條符合條件的記錄。每次取一條記錄。

完整代碼如下:

#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    sqlite3 *db;
    char *zErrMsg = 0;
    int rc;
    const char *sql_select_caller = "select _id, number from calls";
    sqlite3_stmt *stmt;
    const char *tail;

    rc = sqlite3_open("contacts.db", &db);

    if (rc)
    {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        exit(0);
    }
    else
    {
        fprintf(stderr, "Opened database successfully\n");
    }

    //select _id, number from calls
    rc = sqlite3_prepare_v2(db, sql_select_caller, -1, &stmt, &tail);

    rc = sqlite3_step(stmt);
    int ncols = sqlite3_column_count(stmt);

    printf("The column counts of calls is:%d\n", ncols);

    while (rc == SQLITE_ROW)
    {
        printf("calls ID=%d,\t", sqlite3_column_int(stmt, 0));
        printf("number=%s\n", sqlite3_column_text(stmt, 1));
        rc = sqlite3_step(stmt);
    }

    sqlite3_finalize(stmt);

    sqlite3_close(db);
}

輸出如下例:

The column counts of calls is:2
calls ID=1, number=18600009876
calls ID=2, number=18600019876
calls ID=3, number=18600029876
calls ID=4, number=18600039876
calls ID=5, number=18600049876
calls ID=6, number=18600059876
calls ID=7, number=18600069876
calls ID=8, number=18600079876
calls ID=9, number=18600089876
calls ID=10,    number=18600099876

如何寫非查詢語句

上麵的例子是針對查詢語句的,我們再舉個非查詢語句的例子。比如我們試個插入的例子。

void insert_item(sqlite3 *db)
{
    const char *sql_insert_sample = "insert or ignore into calls (_id,number) values (?1,?2);";
    sqlite3_stmt *stmt = NULL;
    const char *tail = NULL;

    int rc = sqlite3_prepare_v2(db, sql_insert_sample, -1, &stmt, &tail);

    sqlite3_bind_int(stmt, 1, 1000);
    sqlite3_bind_text(stmt, 2, "01084993677", 11, NULL);

    rc = sqlite3_step(stmt);

    if (rc == SQLITE_DONE)
    {
        printf("Last inserted row id=%ld\n", (long)sqlite3_last_insert_rowid(db));
    }
    else
    {
        printf("Insert failed!");
    }

    sqlite3_finalize(stmt);
}

輸出如下:

Last inserted row id=0

小結

上麵,我們就查詢和非查詢兩種情況,學習了如何使用SQLite3的API。
剩下的工作主要就是構造SQL語句以及處理返回結果了。

最後更新:2017-04-26 18:00:56

  上一篇:go 數字化革命來襲,公司應該如何應對?
  下一篇:go 基於sklearn的文本特征提取與分類