Sqlite基本使用及性能优化

1.写在前面的话

前面写过一篇关于Sqlite基本操作的文章,今天我们来学习Android中如何使用Sqlite以及性能优化。

2.Android平台下数据库相关类

SQLiteOpenHelper 抽象类:通过从此类继承实现用户类,来提供数据库打开、关闭等操作函数。
SQLiteDatabase 数据库访问类:执行对数据库的插入记录、查询记录等操作。
SQLiteCursor 查询结构操作类:用来访问查询结果中的记录。

3.Android Sqlite的使用

(1)创建数据库

Android下要使用Sqlite首先要写一个SQLiteOpenHelper的实现类,该类的构造函数如下:

private MyDBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
    super(context, name, factory, version);
}

需要传入的参数解释如下:
name:数据库的名称,用这个名称来打开创建或打开相应的数据库。
factory:用来创建cursor,通常情况下我们传入null,使用默认的就行。
version:数据库的版本,从1开始,可以修改版本号,来除法数据库的更新操作。

对于SQLiteOpenHelper我们般会设计成单例。

private static MyDBHelper myDBHelper;

public static synchronized MyDBHelper getInstance(Context context) {
   if (myDBHelper == null) {
            Context applicationContext = context.getApplicationContext();
            myDBHelper = new MyDBHelper(applicationContext);
        }
        return myDBHelper;
    }
private MyDBHelper(Context context) {
        this(context, DB_NAME, null, VERSION);
    }

当我们首次使用MyDBHelper来获取数据库时,即调用getWritableDatabase或getReadableDatabase方法时,变会触发DBHelper中的onCreate方法,这时我们可以在数据库中创建表:

@Override
    public void onCreate(SQLiteDatabase db) {
        StringBuilder sql = new StringBuilder();
        sql.append("create table ");
        sql.append(TAB_PERSON + "(");
        sql.append("id integer,");
        sql.append("name char(8),");
        sql.append("age int");
        sql.append(");");
        db.execSQL(sql.toString());
    }

以上代码实际执行了一个sql语句create table person(id integer,name char(10), age int);创建了一张person表。

(2)更新数据库

通过修改数据库的版本我们可以触发数据库的更新。这里我们修改数据库的version为2,并在更新时添加一个新的developer表。和创建一样,onUpgrade会在调用getWritableDatabase或getReadableDatabase时触发。

 @Override
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        StringBuilder sql = new StringBuilder();
        sql.append("create table ");
        sql.append(TAB_DEVLOPER + "(");
        sql.append("id integer,");
        sql.append("position char(20),");
        sql.append(");");
        db.execSQL(sql.toString());
}

(3)增删查改

对于增删查改,我们可以分别调用数据库的insert、delete、query、update方法传入参数来进行操作,当然也可以直接用execSQL方法来执行Sql来进行操作。比如我们要在在person表中插入一条记录,我们可以在MyDBHelper中创建一个inser方法,如下:

public boolean insert(int id, String name, int age) {
        ContentValues values = new ContentValues();
        values.put("name", name);
        values.put("age", age);
        long insert = getWritableDatabase().insert(TAB_PERSON, null, values);
        return insert >= 1;
    }

4.Sqlite性能优化

(1)编译SQL语句

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

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

编译sql语句获得SQLiteStatement对象,参数使用?代替
在循环中对SQLiteStatement对象进行具体数据绑定,bind方法中的index从1开始,不是0

如下向person表中插入100条数据:

public void insertBatchPreCompile() {
        long start = SystemClock.currentThreadTimeMillis();
        String sql = "insert into " + TAB_PERSON + " values (?,'test','1');";
        SQLiteStatement sqLiteStatement = getReadableDatabase().compileStatement(sql);
        int count = 0;
        while (count < 100) {
            count++;
            sqLiteStatement.clearBindings();
            sqLiteStatement.bindLong(1, count);
            sqLiteStatement.executeInsert();
        }
        Log.e(TAG, "insert recompile use time " + (SystemClock.currentThreadTimeMillis() - start));
    }

(2)显示使用事务

在Android中,无论是使用SQLiteDatabase的insert,delete等方法还是execSQL都开启了事务,来确保每一次操作都具有原子性,使得结果要么是操作之后的正确结果,要么是操作之前的结果。

然而事务的实现是依赖于名为rollback journal文件,借助这个临时文件来完成原子操作和回滚功能。既然属于文件,就符合Unix的文件范型(Open-Read/Write- Close),因而对于批量的修改操作会出现反复打开文件读写再关闭的操作。然而好在,我们可以显式使用事务,将批量的数据库更新带来的journal文件打开关闭降低到1次。

具体的实现代码如下:

public void insertWithTransaction() {
        long start = SystemClock.currentThreadTimeMillis();
        int count = 0;
        try {
            getWritableDatabase().beginTransaction();
            while (count++ < 100) {
                insert(count, "test", 1);
            }
            getWritableDatabase().setTransactionSuccessful();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            getWritableDatabase().endTransaction();
        }
        Log.e(TAG, "insert traceaction use time " + (SystemClock.currentThreadTimeMillis() - start));
    }

使用这两种方式分别优化,对比效果如下:


这里写图片描述

从图中可以看到在插入100条的情况下,使用预编译的方式可以稍微提升性能,但是使用事务,能够使性能提升大概8倍,所以可以看出频繁的IO操作还是比较耗时的。同时使用两种方式进行优化,可以提升17倍,优化效果非常明显。

(3)建立索引

a.索引的概念

索引,使用索引可快速访问数据库表中的特定信息。索引是对数据库表中一列或多列的值进行排序的一种结构。

在关系数据库中,索引是一种与表有关的数据库结构,它可以使对应于表的SQL语句执行得更快。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。当表中有大量记录时,若要对表进行查询,第一种搜索信息方式是全表搜索,是将所有记录一一取出,和查询条件进行一一对比,然后返回满足条件的记录,这样做会消耗大量数据库系统时间,并造成大量磁盘I/O操作;第二种就是在表中建立索引,然后在索引中找到符合查询条件的索引值,最后通过保存在索引中的ROWID(相当于页码)快速找到表中对应的记录。
索引是一个单独的、物理的数据库结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。

索引提供指向存储在表的指定列中的数据值的指针,然后根据您指定的排序顺序对这些指针排序。数据库使用索引的方式与您使用书籍中的索引的方式很相似:它搜索索引以找到特定值,然后顺指针找到包含该值的行。

b.建立索引

创建索引的基本语法:

CREATE INDEX index_name ON table_name;

创建单列索引

CREATE INDEX index_name ON table_name;
c.索引的利弊

毋庸置疑,索引加速了我们检索数据表的速度。然而正如西方谚语 “There are two sides of a coin”,索引亦有缺点:

对于增加,更新和删除来说,使用了索引会变慢,比如你想要删除字

  • 列表内容典中的一个字,那么你同时也需要删除这个字在拼音索引和部首索引中的信息。
  • 建立索引会增加数据库的大小,比如字典中的拼音索引和部首索引实际上是会增加字典的页数,让字典变厚的。
  • 为数据量比较小的表建立索引,往往会事倍功半。

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

(4)查询数据优化

按需获取列信息

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

其中上面方法的第二个参数类型为String[],意思是返回结果参考的colum信息,传递null表明需要获取全部的column数据。如果我们不需要所有列的信息,最好指定一下需要的列。

提前获取索引
例如下面的代码,我们可以把获取列索引的代码cursor.getColumnIndex(TableDefine.COLUMN_INSERT_TIME)放到循环外,这样不需要每次获取。

 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));
    }
}

(5)ContentValues的容量调整

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

(6)及时关闭Cursor

(7)耗时异步化

数据库的操作,属于本地IO,通常比较耗时,如果处理不好,很容易导致ANR,因此建议将这些耗时操作放入异步线程中处理。

本文Demo下载地址SqliteDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容