SQLite優(yōu)化建議

以下是我在使用SQLite過程中磕仅,總結(jié)的一些優(yōu)化建議家妆。

初始的數(shù)據(jù)如下鸵荠,時間單位都是ms。

1.一張user表伤极,只有name和age兩個字段蛹找。

2.同時開啟三條線程測試姨伤。

3.分別測試insert、update庸疾、delete語句的執(zhí)行速度乍楚。

執(zhí)行1000條insert語句,結(jié)果如下:

10-30 14:36:40.436 13041-13771/com.zcbin.dbstudy D/dbTest: insert time:18934

10-30 14:36:40.496 13041-13772/com.zcbin.dbstudy D/dbTest: insert time:18996

10-30 14:36:40.522 13041-13770/com.zcbin.dbstudy D/dbTest: insert time:19023

執(zhí)行1000條update語句届慈,結(jié)果如下

10-30 14:37:26.213 13041-13771/com.zcbin.dbstudy D/dbTest: update time:23824

10-30 14:37:26.247 13041-13772/com.zcbin.dbstudy D/dbTest: update time:23858

10-30 14:37:26.324 13041-13770/com.zcbin.dbstudy D/dbTest: update time:23934

執(zhí)行1000條delete語句炊豪,結(jié)果如下:

10-30 14:37:57.998 13041-13771/com.zcbin.dbstudy D/dbTest: delete time:18479

10-30 14:37:58.012 13041-13770/com.zcbin.dbstudy D/dbTest: delete time:18489

10-30 14:37:58.026 13041-13772/com.zcbin.dbstudy D/dbTest: delete time:18503

使用MultiThread模式

SQLite有三種線程模式,下圖是官網(wǎng)的說明:

線程模式

SQLite的多線程是通過Write-Ahead Logging實現(xiàn)的拧篮,當(dāng)多個線程寫數(shù)據(jù)庫時词渤,會同步執(zhí)行,不過這個同步是指在日志文件中串绩,最后會將日志文件的結(jié)果同步會數(shù)據(jù)庫缺虐。

也就是說將寫數(shù)據(jù)庫的結(jié)果預(yù)先在日志文件中處理了,加快了執(zhí)行速度礁凡。

Android中高氮,如何使用MultiThread模式呢?SQLiteDataBase中有一個方法enableWriteAheadLogging()顷牌,這個方法允許數(shù)據(jù)庫使用Write-Ahead Logging剪芍。

看下測試效果:

執(zhí)行1000條insert語句:


10-30 14:46:05.950 15313-15434/com.zcbin.dbstudy D/dbTest: insert time:6415

10-30 14:46:05.956 15313-15435/com.zcbin.dbstudy D/dbTest: insert time:6418

10-30 14:46:05.957 15313-15436/com.zcbin.dbstudy D/dbTest: insert time:6418

執(zhí)行1000條update語句:


10-30 14:47:04.680 15313-15435/com.zcbin.dbstudy D/dbTest: update time:15033

10-30 14:47:04.791 15313-15434/com.zcbin.dbstudy D/dbTest: update time:15145

10-30 14:47:04.798 15313-15436/com.zcbin.dbstudy D/dbTest: update time:15150

執(zhí)行1000條delete語句:


10-30 14:47:26.596 15313-15435/com.zcbin.dbstudy D/dbTest: delete time:9657

10-30 14:47:26.601 15313-15436/com.zcbin.dbstudy D/dbTest: delete time:9658

10-30 14:47:26.603 15313-15434/com.zcbin.dbstudy D/dbTest: delete time:9661

可以看到,insert提高了3倍窟蓝,delete語句都提高了2倍罪裹,update稍微少一點,從23000減少到15000級运挫。說明執(zhí)行enableWriteAheadLogging()確實能有效提升SQLite執(zhí)行速度状共。

使用事務(wù)

SQLite默認(rèn)一條語句就是一個事務(wù),也就是說谁帕,有多條insert語句時峡继,每條語句都是一個事務(wù),都會去競爭鎖匈挖。在競爭鎖這里消耗了很多時間碾牌。如果對SQLite事務(wù)還不了解,可以看看這篇文章

典型的事務(wù)開啟儡循,使用如下代碼:


try {

    sqLiteDatabase.beginTransaction();

    for (int i = start; i < sum; i++) {

        sqLiteDatabase.execSQL(sql, new String[]{"insertname", i+""});

    }

    sqLiteDatabase.setTransactionSuccessful();

} catch (Exception e) {

    e.printStackTrace();

} finally {

    sqLiteDatabase.endTransaction();

}

暫時不執(zhí)行enableWriteAheadLogging()舶吗,單獨測試一下使用事務(wù)的效果:

執(zhí)行1000條insert語句:


10-30 14:57:09.424 16653-16701/com.zcbin.dbstudy D/dbTest: insert time:77

10-30 14:57:09.485 16653-16703/com.zcbin.dbstudy D/dbTest: insert time:136

10-30 14:57:09.557 16653-16702/com.zcbin.dbstudy D/dbTest: insert time:208

執(zhí)行1000條update語句:


10-30 14:57:36.414 16653-16701/com.zcbin.dbstudy D/dbTest: update time:708

10-30 14:57:37.149 16653-16703/com.zcbin.dbstudy D/dbTest: update time:1443

10-30 14:57:37.863 16653-16702/com.zcbin.dbstudy D/dbTest: update time:2157

執(zhí)行1000條delete語句:


10-30 14:57:51.088 16653-16701/com.zcbin.dbstudy D/dbTest: delete time:578

10-30 14:57:51.473 16653-16703/com.zcbin.dbstudy D/dbTest: delete time:963

10-30 14:57:51.645 16653-16702/com.zcbin.dbstudy D/dbTest: delete time:1134

可以看到,令人驚訝的效果贮折。開啟事務(wù)后裤翩,不存在鎖的競爭,效率一下子提升上去了。所以踊赠,在平常的SQLite操作時呵扛,寫數(shù)據(jù)庫的語句都建議顯示的去處理事務(wù),特別是批量執(zhí)行寫數(shù)據(jù)庫語句時筐带。

使用線程

Android中今穿,如果阻塞了UI線程,會給用戶帶來卡頓的感覺伦籍。所以耗時的任務(wù)都應(yīng)該開啟異步線程去處理蓝晒。(當(dāng)然了,如果僅僅是執(zhí)行簡單的語句帖鸦,比如從一張簡單表里查數(shù)據(jù)芝薇,沒必要開啟額外的線程。)

多線程操作數(shù)據(jù)庫有一個問題作儿。當(dāng)你在A線程執(zhí)行完后close掉數(shù)據(jù)庫洛二,萬一B線程沒執(zhí)行完,但你已經(jīng)close掉了攻锰,這個B線程的數(shù)據(jù)庫操作就會拋異常晾嘶。這種情況下可以給連接計數(shù),每當(dāng)有線程獲得連接娶吞,計數(shù)加一垒迂,每當(dāng)線程close,計數(shù)減一妒蛇。只有計數(shù)為0時机断,才真正關(guān)閉數(shù)據(jù)庫。代碼如下:


public class SQLHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "TEST";

    private static final int DB_VERSION = 1;

    // 使用AtomicInteger當(dāng)做計數(shù)器材部,保證線程安全

    private AtomicInteger dbCount = new AtomicInteger(0);

    private SQLHelper(Context context) {

        super(context, DB_NAME, null, DB_VERSION);

    }

    /**

    * 靜態(tài)內(nèi)部類實現(xiàn)單例

    */

    public static SQLHelper getInstance() {

        SQLHelper instance = SingleHolder.INSTANCE;

        // 每次getInstance時毫缆,連接加一

        instance.dbCount.incrementAndGet(); 

        return instance;

    }

    private static class SingleHolder {

        private static final SQLHelper INSTANCE = new SQLHelper(Application.getContext());

    }

    public void closeDB() {

        // 每當(dāng)調(diào)用closeDB方法時,計數(shù)減一乐导,減為0時,真正關(guān)閉數(shù)據(jù)庫連接

        if (dbCount.decrementAndGet() == 0) {

            super.close();

        }

    }

    @Override

    public void onCreate(SQLiteDatabase db) {

    }

    @Override

    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

}

發(fā)現(xiàn)在有些項目中浸颓,數(shù)據(jù)庫連接直接是不關(guān)閉的物臂,使用中也沒有發(fā)現(xiàn)很大的問題。確實頻繁去open产上、close數(shù)據(jù)庫也是一件耗時的事情棵磷,如果應(yīng)用對數(shù)據(jù)庫依賴較大的話,不關(guān)閉連接可能是更好的解決辦法晋涣。

關(guān)于索引

索引是一種用來在某種條件下加速查詢的結(jié)構(gòu)仪媒。索引是一種特殊的查找表,數(shù)據(jù)庫搜索引擎用來加快數(shù)據(jù)檢索谢鹊。簡單說算吩,索引是一個指向表中數(shù)據(jù)的指針留凭。

如果給某張表的字段建立了索引,可以加快數(shù)據(jù)的檢索速度偎巢。比如一份通訊錄蔼夜,給name建立索引,那么搜索name時压昼,速度提升很大求冷,特別是當(dāng)通訊錄數(shù)據(jù)量較大的時候。

然而索引有兩個主要的缺點:

1. 數(shù)據(jù)改變時窍霞,索引也會改變匠题。比如你執(zhí)行insert增加數(shù)據(jù)時,SQLite會自行更新索引但金,會有一些性能損耗韭山。因此,如果數(shù)據(jù)經(jīng)常發(fā)生變化傲绣,建立索引的代價就很高掠哥。

  1. 索引需要占物理空間,除了數(shù)據(jù)表占據(jù)數(shù)據(jù)空間之外秃诵,每一個索引還要占一定的物理空間续搀,如果要建立聚簇索引,那么需要額空間就會更大菠净。

所以禁舷,索引的使用要謹(jǐn)慎。一般而言毅往,需要經(jīng)常查詢的牵咙,數(shù)據(jù)量大的,更改較少的表適合建立索引攀唯。比如企業(yè)的通訊錄洁桌。

SQLiteStatement

這部分內(nèi)容摘自這篇文章

SQLite想要執(zhí)行操作侯嘀,需要將程序中的SQL語句編譯成對應(yīng)的SQLiteStatement另凌,比如" select * from table1 ",每執(zhí)行一次都需要將這個String類型的SQL語句轉(zhuǎn)換成SQLiteStatement戒幔。如下insert的操作最終都是將ContentValues轉(zhuǎn)成SQLiteStatementi:


public long insertWithOnConflict(String table, String nullColumnHack, 

ContentValues initialValues, int conflictAlgorithm) { 

// 省略部份代碼 

SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); 

try { 

return statement.executeInsert(); 

} finally { 

statement.close(); 

} 

} finally { 

releaseReference(); 

} 

    }

對于批量處理插入或者更新的操作吠谢,我們可以重用SQLiteStatement,使用SQLiteDatabase的beginTransaction()方法開啟一個事務(wù)诗茎,樣例如下:


try

    {

        sqLiteDatabase.beginTransaction();

        SQLiteStatement stat = sqLiteDatabase.compileStatement(insertSQL);

        // 插入10000次

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

        {

            stat.bindLong(1, 123456);

            stat.bindString(2, "test");

            stat.executeInsert();

        }

        sqLiteDatabase.setTransactionSuccessful();

    }

    catch (SQLException e)

    {

        e.printStackTrace();

    }

    finally

    {

        // 結(jié)束

        sqLiteDatabase.endTransaction();

        sqLiteDatabase.close();

    }

從數(shù)據(jù)上看工坊,第四種方式使用SQLiteStatement最快,不過只要添加了事務(wù)(或者說只需要一個事務(wù),不是每條插入都使用事務(wù))王污,后三種方式的差別并不大罢吃。所以針過這個題目的插入的優(yōu)化可以通過“SQLiteStatement+事務(wù)”的方式顯著提高效率。

使用SQLiteStatement只有在大量操作的時候才有意義玉掸,單條語句沒必要使用SQLiteStatement刃麸。GreenDao封裝的執(zhí)行方法中就使用到了SQLiteStatement,感興趣可以去看下源碼司浪。


測試結(jié)果
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泊业,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子啊易,更是在濱河造成了極大的恐慌吁伺,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件租谈,死亡現(xiàn)場離奇詭異篮奄,居然都是意外死亡,警方通過查閱死者的電腦和手機割去,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門窟却,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人呻逆,你說我怎么就攤上這事夸赫。” “怎么了咖城?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵茬腿,是天一觀的道長。 經(jīng)常有香客問我宜雀,道長切平,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任辐董,我火速辦了婚禮悴品,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘简烘。我一直安慰自己他匪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布夸研。 她就那樣靜靜地躺著,像睡著了一般依鸥。 火紅的嫁衣襯著肌膚如雪亥至。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音姐扮,去河邊找鬼絮供。 笑死,一個胖子當(dāng)著我的面吹牛茶敏,可吹牛的內(nèi)容都是我干的壤靶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼惊搏,長吁一口氣:“原來是場噩夢啊……” “哼贮乳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起恬惯,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤向拆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后酪耳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浓恳,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年碗暗,在試婚紗的時候發(fā)現(xiàn)自己被綠了颈将。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡言疗,死狀恐怖晴圾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情洲守,我是刑警寧澤疑务,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站梗醇,受9級特大地震影響知允,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜叙谨,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一温鸽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧手负,春花似錦涤垫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至统捶,卻和暖如春榆芦,著一層夾襖步出監(jiān)牢的瞬間柄粹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工匆绣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留驻右,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓崎淳,卻偏偏與公主長得像堪夭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拣凹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容