SQLite优化建议

作者: carver | 来源:发表于2018-03-11 21:42 被阅读98次

以下是我在使用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,感兴趣可以去看下源码。


测试结果

相关文章

  • SQLite优化建议

    以下是我在使用SQLite过程中,总结的一些优化建议。 初始的数据如下,时间单位都是ms。 1.一张user表,只...

  • FMDB 大量数据操作 卡顿优化

    参考 :sqlite 操作速度优化浅谈iOS 提升SQLite数据插入效率低、速度慢的方法

  • iOS端sqlite3 的线程安全

    sqlite3 多线程和锁 ,优化插入速度及性能优化这篇博客,着重介绍了sqlite3 多线程和锁。这里针对iOS...

  • DBFlow:级联(CASCADE)

    SQLite支持外键 建议查看官网的说明书:SQLite Foreign Key Support 描述外键 利用注...

  • Sqlite索引优化

    背景 sqlite作为客户端常用的数据库,已经成为移动端开发不可避免的一项技能,但是在使用的过程中却常常会出现性能...

  • SQLite性能优化

    一> 如果优化SQLite的性能? (2种方式) Q> 如果我们一次性存储10000条数据, 那么该如何优化自己程...

  • Sqlite 操作优化

    在写项目的时候会经常接触 Sqlite ,比如说写课程表相关的东西,虽然数据不是很多,但是还是可以感觉到短暂的卡顿...

  • Android性能优化-sqlite优化

    一、事务 对于事务,就是数据库的一次原子性的执行操作。原子性的执行操作为数据的整体性执行带来的可靠安全性。在SQL...

  • Android 数据库

    sqlite详解 sqlite升级,增加字段的语句 数据库框架对比和源码分析 数据库的优化 数据库数据迁移问题

  • 数据库语句记录

    创建多列索引 autoincrement 不建议使用CONSTRAINT用意解析SQLite INSERT OR ...

网友评论

    本文标题:SQLite优化建议

    本文链接:https://www.haomeiwen.com/subject/aoxgfftx.html