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文庫


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM