SQLite优化建议

以下是我在使用SQLite过程中,总结的一些优化建议。

初始的数据如下,时间单位都是ms。

1.一张user表,只有name和age两个字段。

2.同时开启三条线程测试。

3.分别测试insert、update、delete语句的执行速度。

执行1000条insert语句,结果如下:

10-30 14:36:40.436 13041-13771/com.zcbin.dbstudy D/dbTest: insert time:18934

10-30 14:36:40.496 13041-13772/com.zcbin.dbstudy D/dbTest: insert time:18996

10-30 14:36:40.522 13041-13770/com.zcbin.dbstudy D/dbTest: insert time:19023

执行1000条update语句,结果如下

10-30 14:37:26.213 13041-13771/com.zcbin.dbstudy D/dbTest: update time:23824

10-30 14:37:26.247 13041-13772/com.zcbin.dbstudy D/dbTest: update time:23858

10-30 14:37:26.324 13041-13770/com.zcbin.dbstudy D/dbTest: update time:23934

执行1000条delete语句,结果如下:

10-30 14:37:57.998 13041-13771/com.zcbin.dbstudy D/dbTest: delete time:18479

10-30 14:37:58.012 13041-13770/com.zcbin.dbstudy D/dbTest: delete time:18489

10-30 14:37:58.026 13041-13772/com.zcbin.dbstudy D/dbTest: delete time:18503

使用MultiThread模式

SQLite有三种线程模式,下图是官网的说明:

线程模式

SQLite的多线程是通过Write-Ahead Logging实现的,当多个线程写数据库时,会同步执行,不过这个同步是指在日志文件中,最后会将日志文件的结果同步会数据库。

也就是说将写数据库的结果预先在日志文件中处理了,加快了执行速度。

Android中,如何使用MultiThread模式呢?SQLiteDataBase中有一个方法enableWriteAheadLogging(),这个方法允许数据库使用Write-Ahead Logging。

看下测试效果:

执行1000条insert语句:


10-30 14:46:05.950 15313-15434/com.zcbin.dbstudy D/dbTest: insert time:6415

10-30 14:46:05.956 15313-15435/com.zcbin.dbstudy D/dbTest: insert time:6418

10-30 14:46:05.957 15313-15436/com.zcbin.dbstudy D/dbTest: insert time:6418

执行1000条update语句:


10-30 14:47:04.680 15313-15435/com.zcbin.dbstudy D/dbTest: update time:15033

10-30 14:47:04.791 15313-15434/com.zcbin.dbstudy D/dbTest: update time:15145

10-30 14:47:04.798 15313-15436/com.zcbin.dbstudy D/dbTest: update time:15150

执行1000条delete语句:


10-30 14:47:26.596 15313-15435/com.zcbin.dbstudy D/dbTest: delete time:9657

10-30 14:47:26.601 15313-15436/com.zcbin.dbstudy D/dbTest: delete time:9658

10-30 14:47:26.603 15313-15434/com.zcbin.dbstudy D/dbTest: delete time:9661

可以看到,insert提高了3倍,delete语句都提高了2倍,update稍微少一点,从23000减少到15000级。说明执行enableWriteAheadLogging()确实能有效提升SQLite执行速度。

使用事务

SQLite默认一条语句就是一个事务,也就是说,有多条insert语句时,每条语句都是一个事务,都会去竞争锁。在竞争锁这里消耗了很多时间。如果对SQLite事务还不了解,可以看看这篇文章

典型的事务开启,使用如下代码:


try {

    sqLiteDatabase.beginTransaction();

    for (int i = start; i < sum; i++) {

        sqLiteDatabase.execSQL(sql, new String[]{"insertname", i+""});

    }

    sqLiteDatabase.setTransactionSuccessful();

} catch (Exception e) {

    e.printStackTrace();

} finally {

    sqLiteDatabase.endTransaction();

}

暂时不执行enableWriteAheadLogging(),单独测试一下使用事务的效果:

执行1000条insert语句:


10-30 14:57:09.424 16653-16701/com.zcbin.dbstudy D/dbTest: insert time:77

10-30 14:57:09.485 16653-16703/com.zcbin.dbstudy D/dbTest: insert time:136

10-30 14:57:09.557 16653-16702/com.zcbin.dbstudy D/dbTest: insert time:208

执行1000条update语句:


10-30 14:57:36.414 16653-16701/com.zcbin.dbstudy D/dbTest: update time:708

10-30 14:57:37.149 16653-16703/com.zcbin.dbstudy D/dbTest: update time:1443

10-30 14:57:37.863 16653-16702/com.zcbin.dbstudy D/dbTest: update time:2157

执行1000条delete语句:


10-30 14:57:51.088 16653-16701/com.zcbin.dbstudy D/dbTest: delete time:578

10-30 14:57:51.473 16653-16703/com.zcbin.dbstudy D/dbTest: delete time:963

10-30 14:57:51.645 16653-16702/com.zcbin.dbstudy D/dbTest: delete time:1134

可以看到,令人惊讶的效果。开启事务后,不存在锁的竞争,效率一下子提升上去了。所以,在平常的SQLite操作时,写数据库的语句都建议显示的去处理事务,特别是批量执行写数据库语句时

使用线程

Android中,如果阻塞了UI线程,会给用户带来卡顿的感觉。所以耗时的任务都应该开启异步线程去处理。(当然了,如果仅仅是执行简单的语句,比如从一张简单表里查数据,没必要开启额外的线程。)

多线程操作数据库有一个问题。当你在A线程执行完后close掉数据库,万一B线程没执行完,但你已经close掉了,这个B线程的数据库操作就会抛异常。这种情况下可以给连接计数,每当有线程获得连接,计数加一,每当线程close,计数减一。只有计数为0时,才真正关闭数据库。代码如下:


public class SQLHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "TEST";

    private static final int DB_VERSION = 1;

    // 使用AtomicInteger当做计数器,保证线程安全

    private AtomicInteger dbCount = new AtomicInteger(0);

    private SQLHelper(Context context) {

        super(context, DB_NAME, null, DB_VERSION);

    }

    /**

    * 静态内部类实现单例

    */

    public static SQLHelper getInstance() {

        SQLHelper instance = SingleHolder.INSTANCE;

        // 每次getInstance时,连接加一

        instance.dbCount.incrementAndGet(); 

        return instance;

    }

    private static class SingleHolder {

        private static final SQLHelper INSTANCE = new SQLHelper(Application.getContext());

    }

    public void closeDB() {

        // 每当调用closeDB方法时,计数减一,减为0时,真正关闭数据库连接

        if (dbCount.decrementAndGet() == 0) {

            super.close();

        }

    }

    @Override

    public void onCreate(SQLiteDatabase db) {

    }

    @Override

    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

}

发现在有些项目中,数据库连接直接是不关闭的,使用中也没有发现很大的问题。确实频繁去open、close数据库也是一件耗时的事情,如果应用对数据库依赖较大的话,不关闭连接可能是更好的解决办法。

关于索引

索引是一种用来在某种条件下加速查询的结构。索引是一种特殊的查找表,数据库搜索引擎用来加快数据检索。简单说,索引是一个指向表中数据的指针。

如果给某张表的字段建立了索引,可以加快数据的检索速度。比如一份通讯录,给name建立索引,那么搜索name时,速度提升很大,特别是当通讯录数据量较大的时候。

然而索引有两个主要的缺点:

1. 数据改变时,索引也会改变。比如你执行insert增加数据时,SQLite会自行更新索引,会有一些性能损耗。因此,如果数据经常发生变化,建立索引的代价就很高。

  1. 索引需要占物理空间,除了数据表占据数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要额空间就会更大。

所以,索引的使用要谨慎。一般而言,需要经常查询的,数据量大的,更改较少的表适合建立索引。比如企业的通讯录。

SQLiteStatement

这部分内容摘自这篇文章

SQLite想要执行操作,需要将程序中的SQL语句编译成对应的SQLiteStatement,比如" select * from table1 ",每执行一次都需要将这个String类型的SQL语句转换成SQLiteStatement。如下insert的操作最终都是将ContentValues转成SQLiteStatementi:


public long insertWithOnConflict(String table, String nullColumnHack, 

ContentValues initialValues, int conflictAlgorithm) { 

// 省略部份代码 

SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); 

try { 

return statement.executeInsert(); 

} finally { 

statement.close(); 

} 

} finally { 

releaseReference(); 

} 

    }

对于批量处理插入或者更新的操作,我们可以重用SQLiteStatement,使用SQLiteDatabase的beginTransaction()方法开启一个事务,样例如下:


try

    {

        sqLiteDatabase.beginTransaction();

        SQLiteStatement stat = sqLiteDatabase.compileStatement(insertSQL);

        // 插入10000次

        for (int i = 0; i < 10000; i++)

        {

            stat.bindLong(1, 123456);

            stat.bindString(2, "test");

            stat.executeInsert();

        }

        sqLiteDatabase.setTransactionSuccessful();

    }

    catch (SQLException e)

    {

        e.printStackTrace();

    }

    finally

    {

        // 结束

        sqLiteDatabase.endTransaction();

        sqLiteDatabase.close();

    }

从数据上看,第四种方式使用SQLiteStatement最快,不过只要添加了事务(或者说只需要一个事务,不是每条插入都使用事务),后三种方式的差别并不大。所以针过这个题目的插入的优化可以通过“SQLiteStatement+事务”的方式显著提高效率。

使用SQLiteStatement只有在大量操作的时候才有意义,单条语句没必要使用SQLiteStatement。GreenDao封装的执行方法中就使用到了SQLiteStatement,感兴趣可以去看下源码。


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

推荐阅读更多精彩内容