Android SQLite总结
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总结的更多相关文章
- Android SQLite 通配符查询找不到参数问题
使用Android SQLite中SQLiteDatabase类的query方法查询时,如果where中包含通配符,则参数会无法设置,如类似下面的方法查询时 SQLiteDatabase db = d ...
- Android+Sqlite 实现古诗阅读应用(三)
往期传送门: Android+Sqlite 实现古诗阅读应用(一) Android+Sqlite 实现古诗阅读应用(二) 加入截图分享的功能. 很多应用都有分享的功能,我也想在我的古诗App里加入这个 ...
- Android+Sqlite 实现古诗阅读应用(二)
传送门:Android+Sqlite 实现古诗阅读应用(一) Hi,又回来了,最近接到很多热情洋溢的小伙伴们的来信,吼开心哈,我会继续努力的=-=! 上回的东西我们做到了有个textview能随机选择 ...
- Android Sqlite 数据库版本更新
Android Sqlite 数据库版本更新 http://87426628.blog.163.com/blog/static/6069361820131069485844/ 1.自己写一个类继承 ...
- Android SQLite总结(一) (转)
Android SQLite总结(一) 郑海波 2012-08-21 转载请声明:http://blog.csdn.net/nuptboyzhb/article/details/7891887 前言 ...
- android SQLite使用SQLiteOpenHelper类对数据库进行操作
android SQLite使用SQLiteOpenHelper类对数据库进行操作 原文: http://byandby.iteye.com/blog/835580
- Android sqlite管理数据库基本用法
Android操作系统中内置了sqlite数据库(有关sqlite数据库详细介绍见:http://zh.wikipedia.org/wiki/SQLite),而sqllite本身是一个很小型的数据库, ...
- Android SQLite 数据库详细介绍
Android SQLite 数据库详细介绍 我们在编写数据库应用软件时,需要考虑这样的问题:因为我们开发的软件可能会安装在很多用户的手机上,如果应用使用到了SQLite数据库,我们必须在用户初次使用 ...
- Android Sqlite 导入CSV文件 .
http://blog.csdn.net/johnnycode/article/details/7413111 今天遇到 Oracle 导出的12万条CSV格式数据导入 Android Sqlite ...
- Android sqlite数据库存取图片信息
Android sqlite数据库存取图片信息 存储图片:bitmap private byte[] getIconData(Bitmap bitmap){ int size = bitmap.get ...
随机推荐
- (中等) POJ 2482 Stars in Your Window,静态二叉树。
Description Here comes the problem: Assume the sky is a flat plane. All the stars lie on it with a l ...
- CodeForces 450B Jzzhu and Sequences
矩阵快速幂. 首先得到公式 然后构造矩阵,用矩阵加速 取模函数需要自己写一下,是数论中的取模. #include<cstdio> #include<cstring> #incl ...
- mongodb 压缩——3.0+支持zlib和snappy
转自:https://scalegrid.io/blog/enabling-data-compression-in-mongodb-3-0/ MongoDB 3.0 with the wired ti ...
- Codeforces Education Round 11
A(模拟+数学) 题意:在一个数列当中最少添加多少个数可以使它们两两互质,并打印出添加以后的数列 #include <iostream> #include <cstdio> # ...
- Badboy安装与使用
Badboy是一个录制web脚本的工具 1.下载Badboy:http://www.badboy.com.au/download/add 2.启动Badboy,认识主界面 3.使用Badboy录制we ...
- html5 安卓拨打电话 发短信
方法一: <input name=”phone_no” format=”*m” value=”13″/> <do type=”option” label=”呼出号”> < ...
- Spring与Struts框架整合
Spring与Struts框架整合 Struts,用Action处理请求 Hibernate,操作数据库 Spring,负责对象创建 Spring与Struts框架整合的关键点在与:让Struts框架 ...
- PHP加水印代码 支持文字和图片水印
PHP加图片水印.文字水印类代码,PHP加水印类,支持文字图片水印的透明度设置.水印图片背景透明.自己写的一个类,因为自己开发的一套CMS中要用到,网上的总感觉用着不顺手,希望大家也喜欢这个类,后附有 ...
- iOS 设置控件圆角、文字、字体
以按钮为例: UIButton btn = [UIButton new]; btn.layer.masksToBounds = YES; btn.layer.cornerRadius = 10.0; ...
- Angular - - form.FormController、ngModel.NgModelController
form.FormController FormController跟踪所有他所控制的和嵌套表单以及他们的状态,就像有效/无效或者脏值/原始. 每个表单指令创建一个FormController实例. ...