一、认识SQLite
SQLite,是一款小型的关系型数据库,在Android、iOS等嵌入式操作系统中使用,内存消耗小,处理速度也比较快,适合手机这种移动设备上使用。是属于内嵌式的数据库,数据库服务器、客户端都运行在同一个进程里面,无需网络配置、管理等操作。
数据库平时用的比较多的都是数据库升级、增删改查等等,简单的使用都不会存在什么问题,业务复杂了以后,就开始出现效率慢、多线程并发等问题,在开发阶段也踩过一些坑,总结一下经验。
二、启动事务
首先了解一下SQLite的操作原理,本身对SQLite的操作就是对文件的读写操作,SQLite是属于文件及别的锁,而且,SQLite默认会为每次的插入、更新操作,都会创建一次事务,完成之后立即提交。整个流程分三个步骤。
① 开启事务、同步数据库文件、打开数据库文件;
② 执行数据库插入、更新等操作;
③ 关闭数据库文件、同步结束、关闭事务。
但开启了事务之后,①跟③都只执行一次,所有插入、更新操作统一都在②执行,因此在速度上比较有优势。
插入5000条数据执行流程如下图
如图所示,频繁的打开、关闭数据库是很消耗时间和资源的,开启事务之后,时间上比未开启事务要缩短t1 * 5000 + t3 * 5000,另外CPU、内存消耗也会相应的减少,可见效率上明显提高了。
开启事务,具有更多其他的优势。例如原子性,一次性提交多个操作,要么全部成功,要么全部失败,失败后会进行回滚到修改之前的状态,避免了脏数据入库的问题。
三、多线程并发
随着业务的发展,复杂的业务不可避免的会出现多线程并发的问题,SQLite是文件级别的锁,从而实现了库级锁,粒度大,支持多线程同时读,但只支持一个线程写,因此在多线程插入、更新操作的时候,往往会出现问题
The database file is locked , database is locked
这是发生了并发操作的错误,如何避免这个问题,需要做到同一时刻只允许一个线程执行插入、更新操作。
1、可以显式使用代码同步synchronized的方式限制单线程执行;
2、每个线程显式采用前面说的事务操作,因为事务本身也会执行同步数据库文件;
3、使用单线程池的方式,所有数据库任务交由一个单线程池执行,通过Handler返回数据在UI线程展示,既不会影响UI线程,也可以防止并发带来的异常。
四、查询优化
1、异步执行,数据库操作在非UI线程执行,这个应该比较好理解;
2、按需获取数据列,可以使用select name from table,就不需要使用select * from table;
3、及时关闭cursor、db,查询完毕之后要及时关闭cursor、db,及时释放资源;
4、编译SQL,使用同一条SQL语句进行插入、更新的批量操作时,可以预先将SQL语句显式编译成SQLiteStatement以重复利用;
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、建立索引,查询数据不用全表扫描,从而大大提高数据库查询效率。但索引会导致插入、更新等操作变慢,维护索引也需要消耗资源,我在实践中比较少利用。
五、结语
在APP业务发展得比较复杂的时候,SQLite的优化显得尤其重要,特别是针对一些需要做本地离线数据、服务器同步等业务的场景。另外,移动端开发的同学也应该多学习SQL语句的使用,在一些数据库连接查询比较多场景,使用数据库框架如ormlite难以实现的时候,再去学习如何拼SQL就显得非常吃力了。又比如,GROUP BY 和 ORDER BY一起使用时,ORDER BY要在GROUP BY的后面这种细节也容易忽视。多积累,走更长的路。