SQLite在Android一般应用中还是比较常用,早期的时候碰到过不少坑,其中最烦的就是多线程并发读写问题,今天正好整理一下,做个笔记,也欢迎指正、讨论和补充。

  一、查询优化

  1、wal模式

  开启wal模式,可以实现并发读,且读写不阻塞,当然写与写之间仍然阻塞,该模式需要android3.0+才支持。

  当开启了wal模式更新数据时,会先将数据写入到*.db-wal文件中,而不是直接修改数据库文件,当执行checkpoint时或某个时间点才会将数据更新到数据库文件(执行endTransaction时会提交checkpoint)。当出现rollback也只是清除wal日志文件,而ROLLBACK JOURNAL模式,也就是关闭wal模式时,当数据有更新时,先将需要修改的数据备份到journal文件中,然后修改数据库文件,当发生rollback,从journal日志中取出数据,并修改数据库文件,然后清除journal日志。 从以上流程来看wal在数据更新上I/O量要小,所以写操作要快。由于在读取数据时也需要读取wal日志验证数据的正确性,所以读取数据相对要慢,但使用wal还是提高了读取的并发性。

  开启wal模式后,一定要使用beginTransactionNonExclusive来提交事务。db.beginTransaction()相当于execSQL("BEGIN EXCLUSIVE;"),在当前事务没有结束之前任何其他线程或进程都无法对数据库进行读写操作。当开启wal模式时,使用db.beginTransactionNonExclusive(),相当于execSQL("BEGIN IMMEDIATE;"),只会限制其他线程对数据库的写操作,不会阻塞读操作。

  2、建立索引,推荐看这个文章,足够了解索引的简单使用和优点了http://www.trinea.cn/android/database-performance/,总而言之,索引会增加SQLite体积,且增删改时也要维护索引,会对增删改性能存在一定影响,如果数据量不大,不建议使用。使用时一定要根据需求建立合适的索引,勿滥用。

  3、当某张表可预见数据量很大时,可以适当的进行表的细化、后期可以分表分库,查询时也可以使用异步查询。

  二、批量插入优化

  1、事务提交

  批量插入,包括更新删除,一定要加事务,如果不加事务,则默认会为每一次插入开启一个事务并自动提交,是非常慢的。

  2、开启wal模式,参见上文中解释;

  3、SQLiteStatement优化

  我们每次执行的sql语句最终会转化为一个SQLiteStatement对象来进行处理,可以预先使用db.compileStatement方法获取SQLiteStatement对象并重用,而不是让系统每次insert都构造一个对应的SQLiteStatement对象,这样能够提高内存的使用率。

  补充:网上有人解释“比如insert into xxx,一般情况下执行多少次,就要编译多少次”,关于这点,首先我认为不对,我阐述一下我自己的分析:SQLite想要执行操作,需要将程序中的sql语句进行“预编译”。例如批量插入,我们可以使用“显式预编译”来做到重用SQLiteStatement,也就是使用compileStatement方法。其实重点在于SQLiteStatement对象在new时,会通过SQLiteSession获取连接池中某个SQLiteConnection,通过调用SQLiteConnection的prepare方法,会从PreparedStatement链表中获取,如果没有可重用的则会创建一个PreparedStatement对象,其中会做一些native操作,例如给PreparedStatement的mStatementPtr赋值,通过注释,我们可以了解到这个mStatementPtr就是一个指向sqlite3_stmt类型的指针,而sqlite3_stmt是sqlite自己内部的数据结构,用来记录“sql语句”,这个sql语句是解析后的,也就是“预编译”后的。

  一句话,就是new SQLiteStatement会对sql做预编译,如果已经预编译过,会直接从缓存链表中拿。

  其实从上面分析,我们知道每个SQLiteConnection都包含一个链表结构的PreparedStatemnt对象集合,每次获取SQLiteConnection都会优先找到包含sql预编译的PreparedStatement实例的数据库连接,这样就不会每次都去预编译sql。所以除非这个connection刚好被其他线程拿去用了,否则都获取相同的connection,不用重复预编译。也就是说在已经执行过一次预编译(生成PreparedStatement实例)的SQLiteConnection中,不会再反复预编译,即使你inser into n次,而导致你需要重新预编译sql的情况是SQLiteConnection恰巧被其他线程使用,就会重新acquirePreparedStatement。

private PreparedStatement acquirePreparedStatement(String sql) {
PreparedStatement statement = mPreparedStatementCache.get(sql);
boolean skipCache = false;
if (statement != null) {
if (!statement.mInUse) {
return statement;
}
// The statement is already in the cache but is in use (this statement appears
// to be not only re-entrant but recursive!). So prepare a new copy of the
// statement but do not cache it.
skipCache = true;
} final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
try {
final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
final int type = DatabaseUtils.getSqlStatementType(sql);
final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
if (!skipCache && isCacheable(type)) {
mPreparedStatementCache.put(sql, statement);
statement.mInCache = true;
}
} catch (RuntimeException ex) {
// Finalize the statement if an exception occurred and we di

  4、分多个db来实现并发写;

  三、getReadableDatabase()和getWritableDatabase()

  getReadableDatabase()和getWritableDatabase()首先都会尝试以读写方式打开数据库。其中getReadableDatabase()如果因为磁盘空间已满等原因导致以读写方式打开数据库失败,会改以只读方式打开,而getWritableDatabase()会抛异常。若只需要一个只读的数据库,可以使用SQLiteDatabase.OPEN_READONLY标志,通过SQLiteDatabase#openDatabase(String, CursorFactory, int)方法手动打开。

  这两个方法成功返回后,会回调onOpen()方法,且OpenHelper会缓存该数据库实例。这两个方法调用时,如果因为数据库文件不存在需要创建会触发SQLiteOpenHelper#onCreate()回调,如果因为数据库版本不一致升或降会触发SQLiteOpenHelper#onUpgrade()、SQLiteOpenHelper#onDowngrade()回调。

  差不多就这些,欢迎大家补充和指正。最后是个人写的SQLite辅助管理类,维护一个SQLiteOpenHelper实例,并提供全局SQLiteDatabase实例打开和关闭,避免因为多线程操作或重复打开关闭导致的database is locked、reopen and already closed等异常,并且支持wal模式。因为比较简单,就不做单独讲解了。

public class SQLiteDbManager
{
private SQLiteDbManager()
{
} private AtomicInteger mOpenCounter = new AtomicInteger(); private static SQLiteDbManager mDatabaseHelper;
private static SQLiteOpenHelper mSQLiteDbMaintain;
private SQLiteDatabase mDatabase; private boolean mEnableWAL; public static void initializeInstance(SQLiteOpenHelper dbMaintain, boolean enableWAL)
{
if (mDatabaseHelper == null)
{
mDatabaseHelper = new SQLiteDbManager();
mDatabaseHelper.mEnableWAL = enableWAL; mSQLiteDbMaintain = dbMaintain;
}
} public static SQLiteDbManager getInstance()
{
return mDatabaseHelper;
} public synchronized SQLiteDatabase openDatabase()
{
if (mOpenCounter.incrementAndGet() == 1)
{
try
{
mDatabase = mSQLiteDbMaintain.getReadableDatabase(); // 并发读
if (mEnableWAL && Build.VERSION.SDK_INT >= 11)
{
mDatabase.enableWriteAheadLogging();
}
}
catch (SQLiteException ex)
{
mOpenCounter.decrementAndGet();
mDatabase = null;
Logger.getInstance().error(ex.toString());
}
}
return mDatabase;
} public void beginTransaction()
{
if (Build.VERSION.SDK_INT >= 11 && mEnableWAL)
{
mDatabase.beginTransactionNonExclusive();
return;
} mDatabase.beginTransaction();
} public void beginTransactionWithListener(SQLiteTransactionListener listener)
{
if (Build.VERSION.SDK_INT >= 11 && mEnableWAL)
{
mDatabase.beginTransactionWithListenerNonExclusive(listener);
return;
} mDatabase.beginTransactionWithListener(listener);
} public synchronized void closeDatabase()
{
if (mOpenCounter.decrementAndGet() == 0)
{
mDatabase.close();
}
}
}

参考链接:

http://www.trinea.cn/android/database-performance/

http://blog.csdn.net/efeics/article/details/18995433

Android SQLite总结的更多相关文章

  1. Android SQLite 通配符查询找不到参数问题

    使用Android SQLite中SQLiteDatabase类的query方法查询时,如果where中包含通配符,则参数会无法设置,如类似下面的方法查询时 SQLiteDatabase db = d ...

  2. Android+Sqlite 实现古诗阅读应用(三)

    往期传送门: Android+Sqlite 实现古诗阅读应用(一) Android+Sqlite 实现古诗阅读应用(二) 加入截图分享的功能. 很多应用都有分享的功能,我也想在我的古诗App里加入这个 ...

  3. Android+Sqlite 实现古诗阅读应用(二)

    传送门:Android+Sqlite 实现古诗阅读应用(一) Hi,又回来了,最近接到很多热情洋溢的小伙伴们的来信,吼开心哈,我会继续努力的=-=! 上回的东西我们做到了有个textview能随机选择 ...

  4. Android Sqlite 数据库版本更新

      Android Sqlite 数据库版本更新 http://87426628.blog.163.com/blog/static/6069361820131069485844/ 1.自己写一个类继承 ...

  5. Android SQLite总结(一) (转)

    Android SQLite总结(一)  郑海波 2012-08-21 转载请声明:http://blog.csdn.net/nuptboyzhb/article/details/7891887 前言 ...

  6. android SQLite使用SQLiteOpenHelper类对数据库进行操作

    android SQLite使用SQLiteOpenHelper类对数据库进行操作 原文: http://byandby.iteye.com/blog/835580

  7. Android sqlite管理数据库基本用法

    Android操作系统中内置了sqlite数据库(有关sqlite数据库详细介绍见:http://zh.wikipedia.org/wiki/SQLite),而sqllite本身是一个很小型的数据库, ...

  8. Android SQLite 数据库详细介绍

    Android SQLite 数据库详细介绍 我们在编写数据库应用软件时,需要考虑这样的问题:因为我们开发的软件可能会安装在很多用户的手机上,如果应用使用到了SQLite数据库,我们必须在用户初次使用 ...

  9. Android Sqlite 导入CSV文件 .

    http://blog.csdn.net/johnnycode/article/details/7413111 今天遇到 Oracle 导出的12万条CSV格式数据导入 Android Sqlite ...

  10. Android sqlite数据库存取图片信息

    Android sqlite数据库存取图片信息 存储图片:bitmap private byte[] getIconData(Bitmap bitmap){ int size = bitmap.get ...

随机推荐

  1. LWIP裸机环境下实现TCP与UDP通讯

    前面移植了LWIP,并且简单的实用了DHCP的功能,今天来使用一下实际的数据通讯的功能 首先是实现TCP客户端,我先上代码 #ifndef __TCP_CLIENT_H_ #define __TCP_ ...

  2. ie6,ie7兼容性总结

    摘自: http://www.cnblogs.com/li0803/archive/2009/08/22/1552094.html 其实浏览器的不兼容,我们往往是各个浏览器对于一些标准的定义不一致导致 ...

  3. BZOJ 1497 [NOI2006]最大获利 ——网络流

    [题目分析] 最大权闭合子图. S到集合1容量为获利的大小,集合2到T为所需要付出的相反数. 然后求出最大流,然后用总的获利相减即可. [代码] #include <cstdio> #in ...

  4. ajax 注册

    $(document).ready(function(e){ $("#uid").blur(function(){ var uid = $("#uid").va ...

  5. [转载] centos6.x x64 安装python2.7

    本文转载自: http://www.centoscn.com/image-text/install/2016/0323/6906.html CentOS的设计理念中有一点是:持久可用.要达到这个目的, ...

  6. JQuery中根据表单元素动态拼接json 字符串

    // <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.o ...

  7. docker网络访问(三)

    docker网络访问 ifconfig查看网卡,启动docker的时候,docker会帮我们创建一个docker0的网桥. 1.随机映射 docker run -P 2.指定映射 -p hostPor ...

  8. UVa 11790 - Murcia's Skyline

    题目大意:给一个建筑的序列,建筑用高度和宽度描述,找出按高度的LIS和LDS,最长XX子序列的长度按照序列中建筑的宽度和进行计算. 其实就是带权的最长XX子序列问题,原来是按个数计算,每个数权都是1, ...

  9. scale等比缩放才能做到看上去能让线条以中心点展开

    .nav-menu>ul>li>a::before {  background: #333 none repeat scroll 0 0;  bottom: -2px;  conte ...

  10. iOS 之 二维码生成与扫描(LBXScan)

    参考:https://github.com/MxABC/LBXScan 步骤如下: 1. 下载 通过参考网址进行下载. 2. 导入 导入整个LBXScan文件夹 3. 配置 在pch中加入 #impo ...