Android应用优化(7)数据库操作

Android设备上,大部分场景都需要进行数据的持久化操作,本地存储一般来说采用sharepreference或者是db(当然自己管理file也是可以的),但是对于稍微复杂一些的数据还是采用数据库的方式保存比较合适。

既然大部分情况下应用都会使用到数据库,那么数据库的优化对于应用性能优化来说就是一个重要的方向了。在Android上默认是使用SQLite数据库,SQLite是一个轻量级的嵌入式数据库,因为它轻量所以比大多数其他类似数据库的操作速度都快一些,其他优缺点这里就不花费篇幅来介绍了,我们主要是应用的角度来指出优化方向。对于数据库的操作,无非来说就是建、增、删、改以及查。下面一一进行分析。

首先说建:建立db文件,建立表。建立数据库文件Android上封装了一个叫SQLiteOpenHelper的类,感兴趣的同学可以详细看看源码,它主要提供了getWritableDatabase()onCreate()onUpgrade()等方法,按照这个模板来写我们只需要注意的一点是:getWritableDatabase()执行时,会去检查数据库是否存在并执行创建文件并执行其onCreate等方法,鉴于我们使用时一般会将其置于一个ContentProvider中,并且这个进程启动时就会创建Provider(见本人本篇所述http://blog.csdn.net/hehui1860/article/details/30728499)进而我们会创建SQLiteOpenHelper这个对象和其他操作。所以在一些开机启动的进程中我们最好不要在创建Provider时就去执行getWritableDatabase()此方法,因为这个方法可能会涉及到文件IO操作(比如第一次创建数据库文件)从而在开机系统和存储繁忙的情况下出现ANR问题,最好的实践应该是在执行第一个数据库操作之前再去进行此操作,这也是懒加载的思想。

还有操作值得提出来,在onCreate()中有可能需要插入一些原始数据,这个时候一般都是直接执行的SQLite语句,SQLite语句就有一个编译执行问题,在需要执行大量相同语句时,大家可以使用SQLiteStatement这种已经预编译的指令来提高重复操作的效率。也可以使用系统提供的DatabaseUtils中的InsertHelper等类来做,原理是一样的。

privatevoidinitTestTable(SQLiteDatabase db) {

   SQLiteStatement insert = db.compileStatement("INSERT INTO test(name, type, "

               + " tag, info) VALUES(?, ?, ?, ?)");

   for(int i = 0; i < 10; i++) {

       insert.clearBindings();

       insert.bindString(1, "Name");

       insert.bindLong(2, 1);

       insert.bindString(3, "Tag");

       insert.bindString(4, "Info");

       insert.executeInsert();

   }

   insert.close();

}


在接下来说增删改查操作之前,我们得提一下一定要特别注意数据库表结构的设计,它直接影响后续数据库操作的性能。数据库表的设计必须具体问题具体分析也没有绝对依循的原则,这里不展开讲了只是提醒大家在动手之前多想想,比如逻辑上需要一起查询的数据不要分为多个表中,这样需要多次查询或者联合查询必然效率要低于单表查询等等。

增删改操作可以放到一起来讲,其实针对这三个操作的优化手段并不是太多,主要就是事务操作,事务操作能提高操作性能得益于一次事务中所有的操作只是在内存缓冲区进行,只有提交才需要进行磁盘操作(就是执行setTransactionSuccessful()之后),众所周知啊,本来就是慢在磁盘读写上,这简直是性能的飞跃。同事它还具有原子操作的特性,这个也是很有用的。

@Override

public ContentProviderResult[] applyBatch(

           ArrayList<ContentProviderOperation> operations)

           throws OperationApplicationException {

   if (operations ==null ||operations.size() < 0 || null == mDatabase) {

       returnnull;

   }

   mDatabase.beginTransaction();

   try {

       int size =operations.size();

       ContentProviderResult[] result = new ContentProviderResult[size];

       for (int i = 0; i < size; i++) {

           ContentProviderOperation opertaion = operations.get(i);

           result[i] = opertaion.apply(this, result, i);

       }

       mDatabase.setTransactionSuccessful();

       return result;

   } finally {

       mDatabase.endTransaction();

   }

}

批量操作性能提升很明显,但是也要注意一些问题,事务量太大,提交时间就会边长,此时数据库是会锁住的,所以事务要及时提交不要贪多。

        查询,查询是数据库操作的一个大头,使用场景多所以也要特别注意它的性能问题。这里主要提两点:利用索引、限制查询参数。

        索引是数据库本身机制,是对记录按照多个字段进行排序的一种展现。对表中的某个字段建立索引会创建另一种数据结构,其中保存着字段的值,每个值还包括指向与它相关记录的指针。这样,就不必要查询整个数据库,自然提升了查询效率。同时,索引的数据结构是经过排序的,因而可以对其执行二分查找,那就更快了。但是索引需要额外开销,并且在增删改时需维护索引造成速度下降。

一个典型的查询是这样的:

db.query(table,columns, selection, selectionArgs, groupBy, having, orderBy);

索引的创建需要注意几个方面

1)很少在查询中使用的列不应创建。

2)含有很少非重复数据值的列不应创建,比如只有01,这时候扫描整表通常会更有效。

3)对于定义为TEXTIMAGE的数据不应该创建索引。这些字段长度不固定,或许很长,或许为空。

4)对于更新操作远大于查询操作时,不建立索引。

5)不需要对声明INTEGER PRIMARY KEY的主键上创建索引。一般Android上都会有一个_id用这个,所以我们根据_id来查询是会用到索引的。

        限制查询参数也对查询效率有着重要影响,对于一个查询首先需要明确columns参数所要查询的列,做到用什么就只查什么,这个差异在列比较多的表中表现尤为明显。并且指定columns之后在读取Cursor数据时显性的指定序号值cursor.getString(Num),更不要在循环中使用cursor.getColumnIndex(“column_name”)

其次是selection,为了使用索引这里的语句也必须注意写法,因为在SQLite中索引要生效必须满足where子句中,前导列(所谓前导列,就是在创建复合索引语句的第一列或者连续的多列。比如通过:CREATE INDEX comp_ind ON table1(x, y,z)创建索引,那么x,xy,xyz都是前导列,而yzyz这样的就不是。)必须使用等于或者in操作,最右边的列可以使用不等式,这样索引才可以完全生效。同时,where子句中的列不需要全建立了索引,但是必须保证建立索引的列之间没有间隙。

下面举个栗子:

CREATE INDEX test ON table (a, b, c, d)

…WHERE a = 5 AND b IN (2, 4) AND cIS NULL AND d='hello'这个语句是可以使用索引的

反例…WHERE a = 5 AND b IN (2, 4) AND c > 9 AND d = 'hello' 这个语句中d的索引是失效的,因为c使用了不等式就认为是在最右边。

再看…WHEREb IN (2, 4) AND cIS NULL AND d='hello'这个语句由于不是前导列所以索引是失效的。

还有对于betweenorlike,都无法使用索引。但是有些情况稍微改造一下就可以咯,请看下面的改法:

CREATE INDEX test ON table (a)

… WHERE a BETWEEN 10 and 20不能使用索引修改为

…WHERE a >= 10 and a <= 20

 

CREATE INDEX test2 ON table2 (b)

…WHERE b = ‘adc’ or b = ‘def’需要修改为

…WHERE b IN (‘abc’, ‘def’)

 

CREATE INDEX test3 ON table3 (c)

…WHERE c LIKE ‘adb%’就可以修改为

…WHERE c >=‘adb’ AND c < ‘adc’

当然上面的栗子只是最简单的形式,复杂的情况还需要多分析看看。

selectionArgs,这个怎么写跟性能无关,这里提一下主要是数据库语法安全和防止一定情况的注入漏洞。所以我们需要将selectionselectionArgs写成这样:

db.query(“table”, “columns”, “id = ? AND text = ? ”, new String[]{“id”, “text”},null, null, null);

        我们一般会在大量查询时出现性能问题,大量查询操作我们可以采取一些小手段带来些许提升。分页查询或者IN语句,就像下面这样:

db.query(“table”, “columns”, “id IN (?,?,?,?) ”, new String[]{“1”, “2”, “3”, “4”},null, null, null);

或者是

db.query(“table”, “columns”, “limit ?,? ”, new String[]{“10”, “20”},null, null, null);

这样查询可比一个个循环查询快多了,另外我们在逻辑中还应该合理设计语句以减少查询次数。

以上只是在个人在使用数据库的过程中一些经验的总结,并不是很全名和深入仅仅从应用的角度讲的,后续可以针对其中某些课题进行深入研究。








你可能感兴趣的:(android,索引,sqlite,事务)