Sqlite簡易性能優化方案,給你的應用插上“翅膀”
highlight: vs theme: devui-blue
本文正在參加「金石計劃 . 瓜分6萬現金大獎」
最近對數據庫進行了一番優化,增加耗時統計,以及優化現有的sql語句操作,減少讀寫耗時,這篇文章就從三個方面來講述下相關的知識點,希望能對你有所幫助。
準備工作
由於下面的優化操作需要一個操作環境,所以我説下當前我做了哪些準備工作:
-
數據表的結構
-
映射的數據Model類
kotlin
class UserInfo {
@JvmField
var xuhao = 0
@JvmField
var name = ""
@JvmField
var height = 0L
@JvmField
var weight = 0.0f
@JvmField
var married = false
@JvmField
var update_time = ""
}
- 準備好的數據
準備工作期間,為了下面的優化對比操作,我已經向該數據庫的表中插入了幾萬條數據:
接下來我們就開始具體的實驗工作吧。
一. 善於使用事務
操作數據庫讀寫時一定要記得開啟事務,事務的好處大家都知道,特別是某次sql指令執行的非常頻繁的時候,開啟事務能極大減少讀寫磁盤的次數,減少讀寫執行耗時。
為了驗證上面的結論,我們分別通過開啟事務和不開啟事務,一次性向數據庫執行2500次update操作,對比下這兩個條件下具體的執行耗時。
1. 不開啟事務
代碼如下:
kotlin
fun updateByCommon() {
for (i in 0..2500) {
val update = "update user_info set update_time = 'qy2_$i' where age = $i"
userDBHelper.writableDatabase.execSQL(update)
}
}
耗時統計使用的是kotlin官方庫提供的擴展函數measureTimeMillis{}
非常的方便:
kotlin
mBinding.tsUpdate.setOnClickListener {
measureTimeMillis {
updateByTs()
}.also {
Log.i("SqliteActivity", "tsUpdate: time = $it")
}
}
接下來我們該場景下的耗時:
我總共執行了4次,差不多不開啟事務一次性執行2500次update操作的耗時平均為7.2s左右。可以看到,相當的耗時。
2. 開啟事務
實驗環境和上面的相同,除了開啟事務來執行2500次update更新操作,先看下實驗代碼:
kotlin
fun updateByTs() {
writeDb.beginTransaction()
for (i in 0..2500) {
val update = "update user_info set update_time = 'qy1_$i' where age = $i"
userDBHelper.writableDatabase.execSQL(update)
}
writeDb.setTransactionSuccessful()
writeDb.endTransaction()
}
最終的執行耗時如下:
執行了4次,每次的平均耗時為5.2s左右,可以看到開啟事務相比較於不開啟事務執行,2500次的update操作耗時減少了大概2s。
這就的優化就結束了嗎?不,我們還可以再減少幾百ms,下面的SQLiteStatement
會帶給你更大的驚喜哈!!
二. 善於顯示創建SQLiteStatement
這個SQLiteStatement
是什麼呢,其實平常我們調用SQLiteDatabase
的insert
、update
等操作,最終都會將sql語句和對應的參數包裝成一個SQLiteStatement
對象,通過該對象執行最終的增刪改查操作:
SQLiteDatabase.update()
源碼:
SQLiteDatabase.insert()
源碼:
從源碼中都會看到,每一次insert
、update
操作都會創建一個SQLiteStatement
對象,像我們第一小節做的實驗,執行了2500次update操作,那就相當於頻繁創建了2500個SQLiteStatement
對象,想想都可怕。
所以這裏的優化就是:在執行update操作之前,顯示創建一個SQLiteStatement
對象,通過?佔位符
和bindXXX
系列方法實現一次創建、多次update的操作。
説的再多不如數説話,我們直接開啟測試,先看下相關代碼:
```kotlin fun updateBySQLiteStatement() { val statement = writeDb.compileStatement("update user_info set update_time = ? where age = ?") writeDb.beginTransaction()
for (i in 0..2500) {
statement.clearBindings()
statement.bindString(1, "qy2_$i")
statement.bindLong(2, i.toLong())
statement.executeUpdateDelete()
}
writeDb.setTransactionSuccessful()
writeDb.endTransaction()
} ```
通過compileStatement()
方法顯示創建一個SQLiteStatement
對象,其中clearBindings()
是清理上次綁定的數據,接下來我們看下執行耗時:
平均耗時為5.1s,相比較開啟事務讀寫,減少了100ms左右,不多但也是一個優化角度。
PS:顯示創建SQLiteStatement
的方式在我們項目中的優化很明顯,至少減少了百分之50的耗時,不知道這裏為啥就優化了那麼一丟丟哎!!
三. 如何準確統計query耗時?
如果我們要統計一個query查詢操作的耗時,直接在query相關的sql語句執行前和執行後統計耗時是不準確的,因為的查詢的耗時還要包括Cursor
的讀取。最正確的方法是在Cursor
執行close()
操作後再去統計查詢耗時。
所以這裏的優化操作是,在基類查詢方法向外部暴漏Cursor
對象時,對這個暴漏的Cursor
藉助靜態代理的思想增加一層包裝,這樣就可以監聽到Cursor
的close()
方法執行時機,在這個方法回調中插入我們最終的
耗時統計代碼:
- 首先創建一個靜態代理對象,並支持傳入
close()
監聽回調
```kotlin class CursorProxy( private val cursor: Cursor, private val mCloseCallback: () -> Unit ) : Cursor by cursor {
override fun close() {
cursor.close()
mCloseCallback()
}
} ```
這裏我們藉助kotlin的by
類委託關鍵字實現了更為簡單的靜態代理,以及通過mCloseCallback
這個函數類型傳入外部的監聽回調代碼,比如這裏的最終耗時統計代碼。
- 在基類查詢方法對外暴漏
CursorProxy
```kotlin public Cursor query(String sql) { //耗時統計開啟 Cursor cursor = db.rawQuery(sql, null);
return new CursorProxy(cursor, new Function0<Unit>() {
@Override
public Unit invoke() {
//耗時統計結束代碼
return null;
}
});
} ```
總結
本篇文章主要是講解了sql操作的一些優化操作,減少執行耗時,同時也給出了準確統計query查詢耗時的優化方案,本篇文章不難理解,希望能對你有所幫助。
- kotlin密封sealed class/interface的迭代之旅
- 2022年12月12日—2022年12月25日Android精品文章一覽
- Sqlite簡易性能優化方案,給你的應用插上“翅膀”
- 築基篇:設置界面的開發利器Preference Library,瞭解一下~
- Android消息機制完整的執行流程,瞭解一下
- 你可能需要了解下的Android開發技巧(二)
- 超有用的Android開發技巧:攔截界面View創建
- Handler創建的幾個必備知識點,瞭解一下
- Android消息機制中Message常用的幾種監控方式
- 數見不鮮的RecyclerView使用技巧,你瞭解嗎(一)?
- 常用到的幾個Kotlin開發技巧,減少對業務層代碼的入侵
- 超好用的官方core-ktx庫,瞭解一下(終)~
- Take a look,從delay()方法看協程的掛起與恢復
- 超好用的官方core-ktx庫,瞭解一下~
- 官方core-ktx庫能對SparseArray系列、Pair開發帶來哪些便利?
- 官方core-ktx庫能對富文本Span開發帶來哪些便利2
- 你需要了解的官方core-ktx庫能對開發帶來哪些便利1
- LeakCanary如何監聽Fragment、Fragment View、ViewModel銷燬時機?
- 非反射動態設置TabLayout指示器的寬度
- 探究EventBus粘性事件實現機制