1. 程式人生 > 其它 >Sqlite3寫效能優化-每秒百萬條寫入

Sqlite3寫效能優化-每秒百萬條寫入

最近專案中使用到了Sqlite3來儲存結果資料,大約100萬條資料,插入到sqlite資料庫中竟然耗時5分鐘,這在真個資料處理流程中佔用了太多的時間,是不可接受的,那麼如何優化sqlite的寫資料的效能呢?

優化方式

通過查閱資料和其他大牛們的部落格,確定有幾個點可以嘗試:

  • 關閉寫同步,PRAGMA synchronous = OFF,在 sqlite3 中 synchronous 有三種模式,分別是 FULL,NORMAL 和 OFF,在系統意外終止的時候,安全性逐級減弱,FULL模式下,保證資料不會損壞,安全性最高,寫入速度也最慢。OFF 模式會比 FULL 模式快50倍以上。
  • 使用事務,如果有許多資料需要插入資料庫,逐條插入,導致頻繁的提交以及磁碟IO,使用事務機制,可以批量插入資料,可以極大的提升寫入速度。實際測試中的情況是,開啟事務之後,寫入速度也可以提升近50倍。
  • 執行準備,執行準備相當於將sql語句提前編譯,省去每次執行sql語句時候的語法檢查等操作,可以極大的優化sql語句的執行效率,其原理有點像 LuaJit 將 Lua 語言成靜態機器碼,提高執行速度。實測情況中,使用執行準備可以提升40倍的寫入速度。
  • 記憶體模式,sqlite3 支援記憶體模式,將資料庫直接建立到記憶體中,開啟地址傳入”:memory:”即可,記憶體模式相比正常模式,可以省區IO的時間,使用記憶體模式的加速思路是,先將資料庫建立到記憶體中,資料寫入完整之後,再呼叫 “VACUUM INTO ‘out.db3’;” 語句將其寫入到磁碟,在開啟了執行準備的情況下,這種方式會稍微快上一點點。

效率對比

使用上面提到的方法,測試下來速度對比如下所示:

優化方法無優化關閉寫同步開啟事務執行準備記憶體模式
每秒插入13條1321條5萬條213萬條215萬條

測試程式碼

錯誤檢查巨集定義:

#define CHECKZERO(a) if((a)!=0) throw("error.");

無優化

sqlite3* db = nullptr;
CHECKZERO(sqlite3_open(path, &db));
CHECKZERO(sqlite3_exec(db, "CREATE TABLE Test(ID INTEGER,var0 INTEGER,var1 REAL,var2 TEXT);", 0, 0, 0));
const int maxcount = 100;
for (int i = 0; i < maxcount; i++) {
    CHECKZERO(sqlite3_exec(db, "INSERT INTO Test (ID,var0,var1,var2) VALUES (0,1,2.0,\\"hello sqlite3.\\");", 0, 0, 0));
}
CHECKZERO(sqlite3_close(db));

關閉寫同步

sqlite3* db = nullptr;
CHECKZERO(sqlite3_open(path, &db));
CHECKZERO(sqlite3_exec(db, "PRAGMA synchronous = OFF", 0, 0, 0));
CHECKZERO(sqlite3_exec(db, "CREATE TABLE Test(ID INTEGER,var0 INTEGER,var1 REAL,var2 TEXT);", 0, 0, 0));
const int maxcount = 10000;
for (int i = 0; i < maxcount; i++) {
    CHECKZERO(sqlite3_exec(db, "INSERT INTO Test (ID,var0,var1,var2) VALUES (0,1,2.0,\\"hello sqlite3.\\");", 0, 0, 0));
}
CHECKZERO(sqlite3_close(db));

開啟事務

sqlite3* db = nullptr;
CHECKZERO(sqlite3_open(path, &db));
CHECKZERO(sqlite3_exec(db, "PRAGMA synchronous = OFF", 0, 0, 0));
CHECKZERO(sqlite3_exec(db, "CREATE TABLE Test(ID INTEGER,var0 INTEGER,var1 REAL,var2 TEXT);", 0, 0, 0));
CHECKZERO(sqlite3_exec(db, "BEGIN", 0, 0, 0));
const int maxcount = 1000000;
for (int i = 0; i < maxcount; i++) {
    CHECKZERO(sqlite3_exec(db, "INSERT INTO Test (ID,var0,var1,var2) VALUES (0,1,2.0,\\"hello sqlite3.\\");", 0, 0, 0));
    if (i % 10000 == 9999) {
        CHECKZERO(sqlite3_exec(db, "COMMIT", 0, 0, 0));
        CHECKZERO(sqlite3_exec(db, "BEGIN", 0, 0, 0));
    }
}
CHECKZERO(sqlite3_exec(db, "COMMIT", 0, 0, 0));
CHECKZERO(sqlite3_close(db));

執行準備

sqlite3* db = nullptr;
CHECKZERO(sqlite3_open(path, &db));
CHECKZERO(sqlite3_exec(db, "PRAGMA synchronous = OFF", 0, 0, 0));
CHECKZERO(sqlite3_exec(db, "CREATE TABLE Test(ID INTEGER,var0 INTEGER,var1 REAL,var2 TEXT);", 0, 0, 0));
// 執行準備
sqlite3_stmt *pPrepare = nullptr;
auto sql = "INSERT INTO Test (ID,var0,var1,var2) VALUES (?,?,?,?);";
CHECKZERO(sqlite3_prepare_v2(db, sql, strlen(sql), &pPrepare, 0));
CHECKZERO(sqlite3_exec(db, "BEGIN", 0, 0, 0));
const int maxcount = 10000000;
for (int i = 0; i < maxcount; i++) {
    CHECKZERO(sqlite3_reset(pPrepare));
    CHECKZERO(sqlite3_bind_int(pPrepare, 1, 0));
    CHECKZERO(sqlite3_bind_int(pPrepare, 2, 1));
    CHECKZERO(sqlite3_bind_double(pPrepare, 3, 2.0));
    const char* str = "hello sqlite3.";
    CHECKZERO(sqlite3_bind_text(pPrepare, 4, str, strlen(str), 0));
    int err = sqlite3_step(pPrepare);
    assert(SQLITE_DONE == err);
    if (i % 10000 == 9999) {
        CHECKZERO(sqlite3_exec(db, "COMMIT", 0, 0, 0));
        CHECKZERO(sqlite3_exec(db, "BEGIN", 0, 0, 0));
    }
}
CHECKZERO(sqlite3_exec(db, "COMMIT", 0, 0, 0));
CHECKZERO(sqlite3_finalize(pPrepare)); // 釋放
CHECKZERO(sqlite3_close(db));

記憶體模式

sqlite3* db = nullptr;
CHECKZERO(sqlite3_open(":memory:", &db));
CHECKZERO(sqlite3_exec(db, "PRAGMA synchronous = OFF", 0, 0, 0));
CHECKZERO(sqlite3_exec(db, "CREATE TABLE Test(ID INTEGER,var0 INTEGER,var1 REAL,var2 TEXT);", 0, 0, 0));
// 執行準備
sqlite3_stmt *pPrepare = nullptr;
auto sql = "INSERT INTO Test (ID,var0,var1,var2) VALUES (?,?,?,?);";
CHECKZERO(sqlite3_prepare_v2(db, sql, strlen(sql), &pPrepare, 0));
CHECKZERO(sqlite3_exec(db, "BEGIN", 0, 0, 0));
const int maxcount = 10000000;
for (int i = 0; i < maxcount; i++) {
    CHECKZERO(sqlite3_reset(pPrepare));
    CHECKZERO(sqlite3_bind_int(pPrepare, 1, 0));
    CHECKZERO(sqlite3_bind_int(pPrepare, 2, 1));
    CHECKZERO(sqlite3_bind_double(pPrepare, 3, 2.0));
    const char* str = "hello sqlite3.";
    CHECKZERO(sqlite3_bind_text(pPrepare, 4, str, strlen(str), 0));
    int err = sqlite3_step(pPrepare);
    assert(SQLITE_DONE == err);
    if (i % 10000 == 9999) {
        CHECKZERO(sqlite3_exec(db, "COMMIT", 0, 0, 0));
        CHECKZERO(sqlite3_exec(db, "BEGIN", 0, 0, 0));
    }
}
CHECKZERO(sqlite3_exec(db, "COMMIT", 0, 0, 0));
CHECKZERO(sqlite3_finalize(pPrepare)); // 釋放
// 匯出
CHECKZERO(sqlite3_exec(db, "VACUUM INTO 'out.db3';", 0, 0, 0));
CHECKZERO(sqlite3_close(db));

總結

sqlite3作為如此強大輕量級的資料庫引擎,插入速度必然不會很慢,如果自己使用過程中發現效率問題,那一定是自己沒有找到合適的用法,在最終的測試結果中,sqlite3的寫入速度達到驚人的200萬條每秒。

完整的測試工程程式碼在此處下載:sqlite3效能優化原始碼資料插入開啟事務執行準備效能提升每秒百萬條資料寫入-其它文件類資源-CSDN文庫