Android SQLite数据库优化

1.利用android提供的的insert,query,update,deleteAPI与execSql,rawQuery函数执行原生的插入,查询,更新,删除语句操作花费时间的对比结果

  在相同的环境(adnroid4.0)和相同的机器下执行相同的动作,记录条数也一样的情况下的对比,多次验证的如下:

  (1)如果批量执行的记录数在1000条,则Android SqliteDatabase提供的insert,query,update,delete函数和直接写SQL文的execSql,rawQuery的效率差不多,几乎一样。所以使用哪种方法都可以,不会影响到执行效率。

  (2)如果批量执行的记录数在10万条,则会存在差别。在某台手机上SqliteDatabase提供的insert执行插入操作耗时45秒,要比execSql插入35秒慢10秒左右。

  可见在数据库大的情况下,还是有差别的。execSql省去了拼接sql语句的步骤,要比SqliteDatabase提供的insert,query,update,delete等函数效率高。当数据库越大,差别也越大。

2.Sqlite数据库批量操作效率的问题:显式使用事务

Android开发中,无论是使用SQLiteDatabase的insert、delete等方法还是execSQL都开启了事务,来确保每一次操作都具有原子性,使得结果要么是操作之后的正确结果,要么是操作之前的结果。然而事务的实现是依赖于名为rollback journal文件,借助这个临时文件来完成原子操作和回滚功能。既然属于文件,就符合Unix的文件范型(Open-Read/Write-Close),对于批量的修改操作会出现反复打开文件读写再关闭的操作,会极大地影响数据库存取的速度。我们可以显式使用事务,将批量的数据库更新带来的journal文件打开关闭降低到1次。

  解决方法:

  添加事务处理,把5000条插入作为一个事务

  我们使用SQLite的事务进行控制:

private void insertWithTransaction(SQLiteDatabase db) {

    int count = 0;

    ContentValues values = new ContentValues();

    try {

        db.beginTransaction();

        while (count++ < 100) {

            values.put(TableDefine.COLUMN_INSERT_TIME, System.currentTimeMillis());

            db.insert(TableDefine.TABLE_RECORD, null, values);

        }

          db.setTransactionSuccessful();

    } catch (Exception e) {

        e.printStackTrace();

    } finally {

        db.endTransaction();

    }

}


         上面的代码中,如果没有异常抛出,我们则认为事务成功,调用 db.setTransactionSuccessful(); 确保操作真实生效。如果在此过程中出现异常,则批量数据一条也不会插入现有的表中。这样SQLite将把全部要执行的SQL语句先缓存在内存当中,然后等到COMMIT的时候一次性的写入数据库,数据库文件只被打开关闭了一次,效率自然大大的提高。

3.建立索引

创建索引的基本语法如下

CREATE INDEX index_name ON table_name;

创建单列索引

CREATE INDEX index_name ON table_name (column_name);

 

      索引真的好么?

 

毋庸置疑,索引加速了我们检索数据表的速度。但任何东西都是有两面性的,索引也有缺点:

1、对于增加,更新和删除来说,使用了索引会变慢,比如你想要删除字典中的一个字,那么你同时也需要删除这个字在拼音索引和部首索引中的信息。

2、建立索引会增加数据库的大小,比如字典中的拼音索引和部首索引实际上是会增加字典的页数,让字典变厚的。

3、为数据量比较小的表建立索引,往往会事倍功半。

所以使用索引需要考虑实际情况进行利弊权衡,对于查询操作量级较大,业务对要求查询要求较高的,还是推荐使用索引的。

4.编译sql语句

SQLite想要执行操作,需要将程序中的sql语句编译成对应的SQLiteStatement,比如 select * from record 这一句,被执行100次就需要编译100次。对于批量处理插入或者更新的操作,我们可以使用显式编译来做到重用SQLiteStatement。

想要做到重用SQLiteStatement也比较简单,基本如下:

编译sql语句获得SQLiteStatement对象,参数使用 ? 代替

在循环中对SQLiteStatement对象进行具体数据绑定,bind方法中的index从1开始,不是0

具体的可参考如下代码:

private void insertWithPreCompiledStatement(SQLiteDatabase db) {

    String sql = "INSERT INTO " + TableDefine.TABLE_RECORD + "( " + TableDefine.COLUMN_INSERT_TIME + ") VALUES(?)";

    SQLiteStatement  statement = db.compileStatement(sql);

    int count = 0;

    while (count < 100) {

        count++;

        statement.clearBindings();

        statement.bindLong(1, System.currentTimeMillis());

        statement.executeInsert();

    }

}

 

5.查询数据优化

对于查询的优化,除了建立索引以外,有以下2点微优化的建议:

1、按需获取数据列信息

通常情况下,我们出于自己省时省力的目的,对于查找使用类似这样的代码

private void badQuery(SQLiteDatabase db) {

    db.query(TableDefine.TABLE_RECORD, null, null, null, null, null, null) ;

}

其中上面方法的第二个参数类型为String[],意思是返回结果参考的colum信息,传递null表明需要获取全部的column数据。这里建议大家传递真实需要的字符串数据对象表明需要的列信息,这样做效率会有所提升。

 

2、提前获取列索引

        当我们需要遍历cursor时,我们通常的做法是这样:

private void badQueryWithLoop(SQLiteDatabase db) {

    Cursor cursor = db.query(TableDefine.TABLE_RECORD, new String[]{TableDefine.COLUMN_INSERT_TIME}, null, null, null, null, null) ;

    while (cursor.moveToNext()) {

        long insertTime = cursor.getLong(cursor.getColumnIndex(TableDefine.COLUMN_INSERT_TIME));

    }

}

但是如果我们将获取ColumnIndex的操作提到循环之外,效果会更好一些,修改后的代码如下:

private void goodQueryWithLoop(SQLiteDatabase db) {

    Cursor cursor = db.query(TableDefine.TABLE_RECORD, new String[]{TableDefine.COLUMN_INSERT_TIME}, null, null, null, null, null) ;

    int insertTimeColumnIndex = cursor.getColumnIndex(TableDefine.COLUMN_INSERT_TIME);

    while (cursor.moveToNext()) {

        long insertTime = cursor.getLong(insertTimeColumnIndex);

    }

    cursor.close();

}

6.ContentValues的容量调整

SQLiteDatabase提供了方便的ContentValues简化了我们处理列名与值的映射,ContentValues内部采用了HashMap来存储Key-Value数据,ContentValues的初始容量是8,如果当添加的数据超过8之前,则会进行双倍扩容操作,因此建议对ContentValues填入的内容进行估量,设置合理的初始化容量,减少不必要的内部扩容操作。

7.及时关闭Cursor

使用数据库,比较常见的就是忘记关闭Cursor。关于如何发现未关闭的Cursor,我们可以使用StrictMode,详细请戳这里 Android性能调优利器StrictMode。

2.耗时异步化

数据库的操作,属于本地IO,通常比较耗时,如果处理不好,很容易导致ANR,因此建议将这些耗时操作放入异步线程中处理,这里推荐一个单线程 + 任务队列形式处理的HandlerThread实现异步化。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值