以下是我在使用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ā)生變化傲绣,建立索引的代價就很高掠哥。
- 索引需要占物理空間,除了數(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,感興趣可以去看下源碼司浪。