轻松提升Sqlite性能:让你的应用飞起来

发表时间: 2022-12-05 22:35

最近对数据库进行了一番优化增加耗时统计,以及优化现有的sql语句操作,减少读写耗时,这篇文章就从三个方面来讲述下相关的知识点,希望能对你有所帮助。

准备工作

由于下面的优化操作需要一个操作环境,所以我说下当前我做了哪些准备工作:

  1. 数据表的结构

  1. 映射的数据Model类
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 = ""}
  1. 准备好的数据

准备工作期间,为了下面的优化对比操作,我已经向该数据库的表中插入了几万条数据:

接下来我们就开始具体的实验工作吧。

一. 善于使用事务

操作数据库读写时一定要记得开启事务,事务的好处大家都知道,特别是某次sql指令执行的非常频繁的时候,开启事务能极大减少读写磁盘的次数,减少读写执行耗时

为了验证上面的结论,我们分别通过开启事务和不开启事务,一次性向数据库执行2500次update操作,对比下这两个条件下具体的执行耗时。

1. 不开启事务

代码如下:

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{}非常的方便:

mBinding.tsUpdate.setOnClickListener {    measureTimeMillis {        updateByTs()    }.also {        Log.i("SqliteActivity", "tsUpdate: time = $it")    }}

接下来我们该场景下的耗时:

我总共执行了4次,差不多不开启事务一次性执行2500次update操作的耗时平均为7.2s左右。可以看到,相当的耗时。

2. 开启事务

实验环境和上面的相同,除了开启事务来执行2500次update更新操作,先看下实验代码:

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是什么呢,其实平常我们调用SQLiteDatabaseinsertupdate等操作,最终都会将sql语句和对应的参数包装成一个SQLiteStatement对象,通过该对象执行最终的增删改查操作:

SQLiteDatabase.update()源码:

SQLiteDatabase.insert()源码:

从源码中都会看到,每一次insertupdate操作都会创建一个SQLiteStatement对象,像我们第一小节做的实验,执行了2500次update操作,那就相当于频繁创建了2500个SQLiteStatement对象,想想都可怕。

所以这里的优化就是:在执行update操作之前,显示创建一个SQLiteStatement对象,通过?占位符bindXXX系列方法实现一次创建、多次update的操作。

说的再多不如数说话,我们直接开启测试,先看下相关代码:

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借助静态代理的思想增加一层包装,这样就可以监听到Cursorclose()方法执行时机,在这个方法回调中插入我们最终的 耗时统计代码:

  1. 首先创建一个静态代理对象,并支持传入close()监听回调
class CursorProxy(    private val cursor: Cursor,    private val mCloseCallback: () -> Unit) : Cursor by cursor {    override fun close() {        cursor.close()        mCloseCallback()    }}

这里我们借助kotlin的by类委托关键字实现了更为简单的静态代理,以及通过mCloseCallback这个函数类型传入外部的监听回调代码,比如这里的最终耗时统计代码。

  1. 在基类查询方法对外暴漏CursorProxy
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查询耗时的优化方案,本篇文章不难理解,希望能对你有所帮助。

作者:长安皈故里
链接:
https://juejin.cn/post/7173460152396300295

来源:稀土掘金

按 Esc 退出