热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SQLite多线程读写

多线程读写SQLite实质上是将数据写入一个文件,通常情况下,在应用的包名下面都能找到xxx.db的文件,拥有root权限的手机,可以通过adbshell,看到datadatapackagena
  • 多线程读写
SQLite 实质上是将数据写入一个文件,通常情况下,在应用的包名下面都能找到 xxx.db 的文件,拥有 root 权限的手机,可以通过 adb shell ,看到 data/data/ packagename /databases/xxx.db 这样的文件。

我们可以得知 SQLite 是文件级别的锁:多个线程可以同时读,但是同时只能有一个线程写。 Android 提供了 SqliteOpenHelper 类,加入 Java 的锁机制以便调用。

如果多线程同时读写(这里的指不同的线程用使用的是不同的 Helper 实例),后面的就会遇到 android.database.sqlite.SQLiteException: database is locked 这样的异常。
对于这样的问题,解决的办法就是 keep single sqlite connection 保持单个 SqliteOpenHelper 实例,同时对所有数据库操作的方法添加 synchronized 关键字。
如下所示:
复制内容到剪贴板
代码:
public class DatabaseHelper extends SQLiteOpenHelper {
        public static final String TAG = "DatabaseHelper";
        private static final String DB_NAME = "practice.db";
        private static final int DB_VERSION = 1;

        private Context mContext;
        private static DatabaseHelper mInstance;

        private DatabaseHelper(Context context) {
                super(context, DB_NAME, null, DB_VERSION);
        }

        public synchronized static DatabaseHelper getInstance(Context context) {
                if (mInstance == null) {
                        mInstance = new DatabaseHelper(context);
                }
                return mInstance;
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
                // TODO Auto-generated method stub

        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                // TODO Auto-generated method stub

        }
public synchronized void queryMethod() {
                SQLiteDatabase readableDatabase = getReadableDatabase();
                //read operation
        }
        
        public void updateMethod() {
                SQLiteDatabase writableDatabase = getWritableDatabase();
                //update operation
        }
}

Android为我们提供了SqliteOpenHelper类,我们可以通过getWritableDatabase 或者 getReadableDatabase 拿到 SQLiteDatabase 对象,然后执行相关方法。这 2 个方法名称容易给人误解,我也在很长的一段时间内想当然的认为 getReadabeDatabase 就是获取一个只读的数据库,可以获取很多次,多个线程同时读,用完就关闭,实际上 getReadableDatabase 先以读写方式打开数据库,如果数据库的磁盘空 间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。 复制内容到剪贴板
代码:
public synchronized SQLiteDatabase getReadableDatabase() {
        if (mDatabase != null && mDatabase.isOpen()) {
            return mDatabase;  // The database is already open for business
        }

        if (mIsInitializing) {
            throw new IllegalStateException("getReadableDatabase called recursively");
        }

        try {
            return getWritableDatabase();
        } catch (SQLiteException e) {
            if (mName == null) throw e;  // Can't open a temp database read-only!
            Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);
        }

        SQLiteDatabase db = null;
        try {
            mIsInitializing = true;
            String path = mContext.getDatabasePath(mName).getPath();
            db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
            if (db.getVersion() != mNewVersion) {
                throw new SQLiteException("Can't upgrade read-only database from version " +
                        db.getVersion() + " to " + mNewVersion + ": " + path);
            }

            onOpen(db);
            Log.w(TAG, "Opened " + mName + " in read-only mode");
            mDatabase = db;
            return mDatabase;
        } finally {
            mIsInitializing = false;
            if (db != null && db != mDatabase) db.close();
        }
    }

在多线程中,如果第一个线程先调用getWritableDatabase,后面线程再次调用,或者第一个线程先调用getReadableDatabase,后面的线程调用getWritableDatabase,那么后面的这个方法是会失败的,因为数据库文件打开后会加锁,必须等前面的关闭后后面的调用才能正常执行,正是因为这个原因,可以1 Write+Many Read(有可能产生冲突,因为第一个getReadableDatabase有可能先于getWritableDatabase执行,导致后面的失败),也可以Many Read,但是不可能Many Write。所以使用单例加上同步的数据库操作方法,就不会出现死锁的问题,这部分例子请参照附件,多线程可以运行的很好,另外关于Sqlite database locking collisions example,网上有很不错的一个例子,可以 这里 去下载。

其实我觉得理论上可以修改getReadableDatabase方法,打开的数据库都是Read Only的,这样就能同时1 Write+Many Read,只不过要保证打开之前,数据库要创建或者升级好,这样读操作就不会互斥写操作,效率相对更高。
关于数据库关闭的问题,在下面好的习惯中会专 门说明。

  • 事务
接触过数据库的人,对事务这个概念一定不陌生,它是原子性的,要么执行成功,执行一半失败后会回滚,这样就能保证数据的完整性。 SQLiteDatabase 也提供了 Transaction 的相关方法,常见用法:
复制内容到剪贴板
代码:
db.beginTransaction();
   try {
     ...
     db.setTransactionSuccessful();
   } finally {
     db.endTransaction();
   }

使用事务对于批量更新有极大的好处,因为单次更新会频繁的调用数据库,曾经我同步过联系人,没使用事务之前,300个联系人写入自己的数据库大概需要3~5秒钟的时间,引入事务后,读取联系人的时间没有减少,但是所有更新的时间降为200ms级,提升极为明显。

  • 升级
在应用迭代多个版本后,随着功能的增加和改变,数据库改变是很常见的事情,由于数据库中的数据一般是需要永久保存的,应用更新后,用户不希望数据丢失,特别是如果应用有几十万,百万级的用户量,如果很粗鲁的丢弃旧版本数据库中数据,对用户体验是很不好的,如果你没有提供云端备份的方案,就需要为用户保留旧的数据,即便数据库结构要发生变化。
实际上多次数据库变动的升级是很痛苦的事情,要考虑每一个旧的版本,理论上用户可以从任何一个旧的版本直接升级到最新版本,我们需要考虑每一种情况。 onUpgrade 方法中,针对每一种版本号,先把旧的临时数据保存下来,删去旧的表,创建新表,然后将数据根据情况插入到新表中,不需要的字段可以丢弃,新增字段填默认值,数据可以临时存放到一个数组中,或者可以临时 cache 到文件中,最后将临时文件清空。
更新操作可以使用事务提高效率,另外需要知道的是 I/O 操作时耗时的,如果数据量较大,还需要放到单独的线程中处理,防止阻塞 UI

  • 数据初始化
我们也经常会遇到数据库中需要初始化数据,比如城市,机场,号码归属地等信息,如果数据量不是很大,我们可以处理后放到asset 或者raw 文件下,创建数据库后导入进去,并且在2.3 以前,asset 中文件有大小限制,文件大小不能超过1M ,否则AssetManager Resources classes 方法来获取InputStream ,将抛出DEBUG/asset(1123): Data exceeds UNCOMPRESS_DATA_MAX java.io.IOException 异常。

解决这个问题有 4 个方法:
1.改名称(最简单):
aapt 工具在打包 apk 文件时,会将资源文件压缩以减小安装包大小( raw 文件夹下的资源则不受影响)。 但是可以通过修改文件成下面的扩展名,逃避检查。
复制内容到剪贴板
代码:
/* these formats are already compressed, or don't compress well */

  static const char* kNoCompressExt[] = {
  ".jpg", ".jpeg", ".png", ".gif",
  ".wav", ".mp2", ".mp3", ".ogg", ".aac",
  ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
  ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
  ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
  ".amr", ".awb", ".wma", ".wmv"
  };

2.压缩:
如果原文件能压缩到1M一下,可以先压缩成zip或者rar格式,然后解压将数据库文件释放到相应位置。
3.分割文件:
大的数据,分割成多个小数据文件,info1.dat,info2.dat…,分别读取这些文件数据插入数据库。
4.网络:
上面的几种方法都是将初始化数据放在安装包中,这样无疑会增加安装包大小,如果必要情况下,可以将数据放到服务器上,创建数据库后,通过HTTP请求,获取JSON,XML数据或者数据库文件,然后经过处理入库。

  • 除此之外要有几点要注意

1.关闭Cursor
Cursor如果不关闭,虽然不会导致出错,但是Log中会有错误提示,还是严谨点,Activity中有startManagingCursor的方法,Activity会在生命周期结束时关闭这些Cursor,其他地方,我们则需要用完关闭,以前需要CursorAdapter则需要在changeCursor时判断关闭old cursor,在ActivityonDestory方法中关闭cursor
2.关闭DatabaseHelper
在上述单例Helper例子中,其实一直没有关闭数据库,但是我们阅读getReadabeDatabasegetWritableDatabas的方法,他们会关闭Old SQLiteDatabase的,我们只需要在ApplicationonTerminal方法中关闭即可,这样也能避免多线程中,一个线程关闭了数据库,导致其他线程使用的时候失败的问题。
实质上,数据库是一个文件引用,单例模式下,不关闭也不会出现问题,让它保持随单例的生命周期关闭就好了。
3.在循环外面获取ColumnIndex,如果表中列不是很多,每次查询又返回所有列的话,可以将列的index定义到TABLE_COLUMNS中去,这样每次获取指定列数据的话,就不用去查找index了。
4.数据库存放的数据类型
Android提供了多种数据存储的方法,文件,数据库,SharePreference,网络等,要根据情况选择合适的方式,不要把什么东西都往数据库中塞。
下面的几种情况就不适合放到数据库中:
1)图片等二进制数据:如果是图片的话,可以将文件名称或者路径保存到数据库中,真正的文件可以作为缓存文件保存在文件系统中。
2)临时数据:定位获取到的Location,登录的Session等。
3)日志数据:可以写入文件中,通常是log_xxxx.txt

 

demo:http://bbs.51cto.com/thread-990260-1.html


推荐阅读
  • 解决.net项目中未注册“microsoft.ACE.oledb.12.0”提供程序的方法
    在开发.net项目中,通过microsoft.ACE.oledb读取excel文件信息时,报错“未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序”。本文提供了解决这个问题的方法,包括错误描述和代码示例。通过注册提供程序和修改连接字符串,可以成功读取excel文件信息。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • Django 入门学习(3)
    在例4的基础上,我希望直接保存数据到数据库中。Django自带了一个sqlite的数据库,已经配置好了可以直接使用。使用方式和sqlalchemy类似,相当于一 ... [详细]
  • 对于很多想做通讯录,或者很多想对系统通讯录操作的童鞋们肯定都会遇到个问题,系统通讯录操作很麻烦,我能不能直接看看底层联系人表结构呢?如果可以看到的话,那一定能提高相关操作的准确性和效率吧。  不多说, ... [详细]
  • iOS关于移动端SQLite,你想知道的都有
    一、SQLite简介SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目 ... [详细]
  • 转自:http:www.cnblogs.comzeroonep4316346.html如果一个.NET应用要自适应32位64位系统,只需要在项目的“目标平台”设置为“AnyCPU”。但是 ... [详细]
  • 本文介绍了在Win10上安装WinPythonHadoop的详细步骤,包括安装Python环境、安装JDK8、安装pyspark、安装Hadoop和Spark、设置环境变量、下载winutils.exe等。同时提醒注意Hadoop版本与pyspark版本的一致性,并建议重启电脑以确保安装成功。 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • 背景使用sqlite3的命令实现数据去重,与无效数据删除等操作。所有操作均封装在shellscript中。创建数据库邮件数据库:UserEmail.dbEmail表:TABLE_EM ... [详细]
  • 数据库锁在SQLite中,锁和事务是紧密联系的。为了有效地使用事务,需要了解一些关于如何加锁的知识。SQLite采用粗放型的锁。当一个连接要写数据库,所有其它的连接被锁住,直到写连接 ... [详细]
  • SQLite数据库查看工具
    http:blog.csdn.netteresa502articledetails7519413SQLite的官方网站http:www.sqlite.orgSQLite的官方网址列 ... [详细]
author-avatar
PLDLYY
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有