首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 数据库 > SQL Server >

SQLite多线程读写实践及常见有关问题总结

2013-10-30 
SQLite多线程读写实践及常见问题总结多线程读写SQLite实质上是将数据写入一个文件,通常情况下,在应用的包

SQLite多线程读写实践及常见问题总结

  • 多线程读写

    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关键字。

    如下所示:

    ?

    Java代码??SQLite多线程读写实践及常见有关问题总结
    1. public?class?DatabaseHelper?extends?SQLiteOpenHelper?{??
    2. ????????public?static?final?String?TAG?=?"DatabaseHelper";??
    3. ????????private?static?final?String?DB_NAME?=?"practice.db";??
    4. ????????private?static?final?int?DB_VERSION?=?1;??
    5. ??
    6. ????????private?Context?mContext;??
    7. ????????private?static?DatabaseHelper?mInstance;??
    8. ??
    9. ????????private?DatabaseHelper(Context?context)?{??
    10. ????????????????super(context,?DB_NAME,?null,?DB_VERSION);??
    11. ????????}??
    12. ??
    13. ????????public?synchronized?static?DatabaseHelper?getInstance(Context?context)?{??
    14. ????????????????if?(mInstance?==?null)?{??
    15. ????????????????????????mInstance?=?new?DatabaseHelper(context);??
    16. ????????????????}??
    17. ????????????????return?mInstance;??
    18. ????????}??
    19. ??
    20. ????????@Override??
    21. ????????public?void?onCreate(SQLiteDatabase?db)?{??
    22. ????????????????//?TODO?Auto-generated?method?stub??
    23. ??
    24. ????????}??
    25. ??
    26. ????????@Override??
    27. ????????public?void?onUpgrade(SQLiteDatabase?db,?int?oldVersion,?int?newVersion)?{??
    28. ????????????????//?TODO?Auto-generated?method?stub??
    29. ??
    30. ????????}??
    31. public?synchronized?void?queryMethod()?{??
    32. ????????????????SQLiteDatabase?readableDatabase?=?getReadableDatabase();??
    33. ????????????????//read?operation??
    34. ????????}??
    35. ??????????
    36. ????????public?void?updateMethod()?{??
    37. ????????????????SQLiteDatabase?writableDatabase?=?getWritableDatabase();??
    38. ????????????????//update?operation??
    39. ????????}??
    40. }??


    Android为我们提供了SqliteOpenHelper类,我们可以通过getWritableDatabase或者getReadableDatabase拿到SQLiteDatabase对象,然后执行相关方法。这2个方法名称容易给人误解,我也在很长的一段时间内想当然的认为getReadabeDatabase就是获取一个只读的数据库,可以获取很多次,多个线程同时读,用完就关闭,实际上getReadableDatabase先以读写方式打开数据库,如果数据库的磁盘空间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。

    ?

    ?

    Java代码??SQLite多线程读写实践及常见有关问题总结
    1. public?synchronized?SQLiteDatabase?getReadableDatabase()?{??
    2. ????????if?(mDatabase?!=?null?&&?mDatabase.isOpen())?{??
    3. ????????????return?mDatabase;??//?The?database?is?already?open?for?business??
    4. ????????}??
    5. ??
    6. ????????if?(mIsInitializing)?{??
    7. ????????????throw?new?IllegalStateException("getReadableDatabase?called?recursively");??
    8. ????????}??
    9. ??
    10. ????????try?{??
    11. ????????????return?getWritableDatabase();??
    12. ????????}?catch?(SQLiteException?e)?{??
    13. ????????????if?(mName?==?null)?throw?e;??//?Can't?open?a?temp?database?read-only!??
    14. ????????????Log.e(TAG,?"Couldn't?open?"?+?mName?+?"?for?writing?(will?try?read-only):",?e);??
    15. ????????}??
    16. ??
    17. ????????SQLiteDatabase?db?=?null;??
    18. ????????try?{??
    19. ????????????mIsInitializing?=?true;??
    20. ????????????String?path?=?mContext.getDatabasePath(mName).getPath();??
    21. ????????????db?=?SQLiteDatabase.openDatabase(path,?mFactory,?SQLiteDatabase.OPEN_READONLY);??
    22. ????????????if?(db.getVersion()?!=?mNewVersion)?{??
    23. ????????????????throw?new?SQLiteException("Can't?upgrade?read-only?database?from?version?"?+??
    24. ????????????????????????db.getVersion()?+?"?to?"?+?mNewVersion?+?":?"?+?path);??
    25. ????????????}??
    26. ??
    27. ????????????onOpen(db);??
    28. ????????????Log.w(TAG,?"Opened?"?+?mName?+?"?in?read-only?mode");??
    29. ????????????mDatabase?=?db;??
    30. ????????????return?mDatabase;??
    31. ????????}?finally?{??
    32. ????????????mIsInitializing?=?false;??
    33. ????????????if?(db?!=?null?&&?db?!=?mDatabase)?db.close();??
    34. ????????}??
    35. ????}??


    在多线程中,如果第一个线程先调用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的相关方法,常见用法:

      ?

      ?

      Java代码??SQLite多线程读写实践及常见有关问题总结
      1. db.beginTransaction();??
      2. ???try?{??
      3. ?????...??
      4. ?????db.setTransactionSuccessful();??
      5. ???}?finally?{??
      6. ?????db.endTransaction();??
      7. ???}??


      使用事务对于批量更新有极大的好处,因为单次更新会频繁的调用数据库,曾经我同步过联系人,没使用事务之前,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文件夹下的资源则不受影响)。但是可以通过修改文件成下面的扩展名,逃避检查。

          ?

          ?

          Java代码??SQLite多线程读写实践及常见有关问题总结
          1. /*?these?formats?are?already?compressed,?or?don't?compress?well?*/???
          2.   static?const?char*?kNoCompressExt[]?=?{???
          3.   ".jpg",?".jpeg",?".png",?".gif",???
          4.   ".wav",?".mp2",?".mp3",?".ogg",?".aac",???
          5.   ".mpg",?".mpeg",?".mid",?".midi",?".smf",?".jet",???
          6.   ".rtttl",?".imy",?".xmf",?".mp4",?".m4a",???
          7.   ".m4v",?".3gp",?".3gpp",?".3g2",?".3gpp2",???
          8.   ".amr",?".awb",?".wma",?".wmv"???
          9.   };??


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

          ?

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

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

            ?

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

热点排行