在寫(xiě)項(xiàng)目的時(shí)候會(huì)經(jīng)常接觸 Sqlite ,比如說(shuō)寫(xiě)課程表相關(guān)的東西,雖然數(shù)據(jù)不是很多,但是還是可以感覺(jué)到短暫的卡頓,所以想著通過(guò)一些方法來(lái)提高 Sqlite 的速度。
使用事務(wù)
引用別人博客里面的話(huà)
在Android中,無(wú)論是使用SQLiteDatabase的insert,delete等方法還是execSQL都開(kāi)啟了事務(wù),來(lái)確保每一次操作都具有原子性,使得結(jié)果要么是操作之后的正確結(jié)果,要么是操作之前的結(jié)果。
然而事務(wù)的實(shí)現(xiàn)是依賴(lài)于名為rollback journal文件,借助這個(gè)臨時(shí)文件來(lái)完成原子操作和回滾功能。既然屬于文件,就符合Unix的文件范型(Open-Read/Write-Close),因而對(duì)于批量的修改操作會(huì)出現(xiàn)反復(fù)打開(kāi)文件讀寫(xiě)再關(guān)閉的操作。然而好在,我們可以顯式使用事務(wù),將批量的數(shù)據(jù)庫(kù)更新帶來(lái)的journal文件打開(kāi)關(guān)閉降低到1次。
別人的博客里面其實(shí)說(shuō)的已經(jīng)很清楚了,但是最為影響數(shù)據(jù)庫(kù)時(shí)間效率的是反復(fù)操作 journal 文件,所以如果我們手動(dòng)控制事務(wù)的開(kāi)啟和關(guān)閉,就可以減少 journal 文件的開(kāi)啟和關(guān)閉到一次。
實(shí)例代碼如下
public void insertWithTransaction() {
long startTime = System.currentTimeMillis();
try{
db.beginTransaction();
ContentValues contentValues = new ContentValues();
for (UserBean userBean: userBeanList) {
contentValues.put(TestDBSchema.UserTable.Cols.ID, userBean.getId());
contentValues.put(TestDBSchema.UserTable.Cols.NAME, userBean.getName());
contentValues.put(TestDBSchema.UserTable.Cols.GENDER, userBean.getGender());
contentValues.put(TestDBSchema.UserTable.Cols.AGE, userBean.getAge());
contentValues.put(TestDBSchema.UserTable.Cols.PHONE, userBean.getPhone());
contentValues.put(TestDBSchema.UserTable.Cols.REGION, userBean.getRegion());
db.insert(TestDBSchema.UserTable.NAME, null, contentValues);
}
db.setTransactionSuccessful();
} catch (SQLException e) {
e.printStackTrace();
} finally {
db.endTransaction();
}
long endTime = System.currentTimeMillis();
long cost = endTime - startTime;
transactionTextView.setText("" + cost);
}
重用 SQLiteStatement
在網(wǎng)上查找這方面的資料,介紹使用 SQLiteStatement 減少 SQL 語(yǔ)句的編譯次數(shù),從而提高效率。比如說(shuō)我們平時(shí)插入數(shù)據(jù)的時(shí)候會(huì)在循環(huán)中反復(fù)調(diào)用 execSQL 這個(gè)函數(shù),這樣只要循環(huán)多少遍,就會(huì)將 SQL 語(yǔ)句編譯多少遍,使用
SQLiteStatement 可以將這個(gè)的次數(shù)有效的減為一次。當(dāng)然也要搭配上面介紹的事務(wù)。
實(shí)例代碼如下所示
public void insertWithStatement() {
long startTime = System.currentTimeMillis();
SQLiteStatement statement = db.compileStatement(SQL_STATEMENT);
try {
db.beginTransaction();
for (UserBean userBean: userBeanList) {
statement.clearBindings();
statement.bindLong(1, userBean.getId());
statement.bindString(2, userBean.getName());
statement.bindString(3, userBean.getGender());
statement.bindLong(4, userBean.getAge());
statement.bindString(5, userBean.getPhone());
statement.bindString(6, userBean.getRegion());
statement.executeInsert();
}
db.setTransactionSuccessful();
} catch (SQLException e) {
e.printStackTrace();
} finally {
db.endTransaction();
}
long endTime = System.currentTimeMillis();
long cost = endTime - startTime;
statementTextView.setText("" + cost);
}
時(shí)間效率的介紹

從效果圖可以看出,相比沒(méi)有自己手動(dòng)開(kāi)啟事務(wù)相比,手動(dòng)控制開(kāi)啟事務(wù)效率真的高太多了,接著如果你是用了 SQLiteStatement ,效率又會(huì)有小幅的提升,最后附上我的實(shí)例代碼
其它的優(yōu)化技巧
通過(guò)索引提高查詢(xún)的速度
就像以前查字典的時(shí)候會(huì)查找拼音索引,簡(jiǎn)單的就是針對(duì)表里面的一列或者兩列創(chuàng)建一個(gè)目錄一樣的東西(貌似形容的有點(diǎn)不準(zhǔn)確),當(dāng)我們使用 select 語(yǔ)句查詢(xún)的時(shí)候就可以在這個(gè)目錄里面查,不用管其它的幾列,效率自然提升。這里用 SQLite 代碼簡(jiǎn)單的舉個(gè)例子就可以了。
這里借用菜鳥(niǎo)教程里面的例子,假設(shè)有表 COMPANY,我們將創(chuàng)建一個(gè)索引,并用它進(jìn)行 INDEXED BY 操作。
創(chuàng)建索引
CREATE INDEX salary_index ON COMPANY(salary);
依靠索引查詢(xún)
SELECT * FROM COMPANY INDEXED BY salary_index WHERE salary > 5000;
索引的缺點(diǎn)
- 對(duì)于增加,更新和刪除來(lái)說(shuō),使用了索引會(huì)變慢,這也很好想到,你在刪除表里面的數(shù)據(jù)的時(shí)候要順帶刪除相應(yīng)的索引。
- 建立索引會(huì)增加數(shù)據(jù)庫(kù)的大小。
- 為數(shù)據(jù)量比較小的表建立索引,往往會(huì)事倍功半。
在線(xiàn)程里面處理
數(shù)據(jù)庫(kù)屬于本地 IO ,也是比較耗時(shí)的,可能會(huì)引發(fā) ANR , 所以如果數(shù)據(jù)量十分大的情況下可以在異步處理,可以考慮使用單線(xiàn)程+任務(wù)隊(duì)列的形式的 HandlerThread 來(lái)實(shí)現(xiàn)。