对于那些使用Android API的人:
SQLite 中的锁定是在文件级别完成的,这保证了锁定
来自不同线程和连接的变化。从而多
线程可以读取数据库,但只能写入。
更多关于锁定 SQLite 的信息可以在SQLite 文档中阅读,但我们最感兴趣的是 OS Android 提供的 API。
可以从单个和多个数据库连接中使用两个并发线程进行写入。由于只有一个线程可以写入数据库,因此有两种变体:
- 如果您从一个连接的两个线程写入,则一个线程将
等待另一个完成写作。
- 如果您从不同连接的两个线程写入,则会出现错误
will be – 您的所有数据都不会写入数据库,并且
应用程序将被中断
SQLiteDatabaseLockedException。很明显,
应用程序应始终只有一份
SQLiteOpenHelper(只是一个打开的连接)否则
SQLiteDatabaseLockedException 随时可能发生。
单个 SQLiteOpenHelper 的不同连接
每个人都知道 SQLiteOpenHelper 有 2 个方法提供对数据库 getReadableDatabase() 和 getWritableDatabase() 的访问,分别读取和写入数据。然而,在大多数情况下,只有一个真正的联系。而且它是同一个对象:
SQLiteOpenHelper.getReadableDatabase()==SQLiteOpenHelper.getWritableDatabase()
这意味着读取数据的方法的使用没有区别。然而,还有另一个更重要的未记录问题——在 SQLiteDatabase 类内部有自己的锁——变量 mLock。在对象 SQLiteDatabase 级别进行写入锁定,并且由于只有一个 SQLiteDatabase 副本用于读取和写入,因此数据读取也被阻止。在事务中写入大量数据时,它会更加突出。
让我们考虑这样一个应用程序的示例,它应该在首次启动时在后台下载大量数据(大约 7000 行包含 BLOB)并保存它到数据库。如果数据保存在事务中,则保存大约需要。 45 秒,但用户无法使用该应用程序,因为任何阅读查询都被阻止。如果数据以小部分保存,则更新过程会拖出相当长的时间(10-15 分钟),但用户可以使用该应用程序而没有任何限制和不便。 “双刃剑”——快速或方便。
Google 已经修复了与 SQLiteDatabase 功能相关的部分问题,添加了以下方法:
beginTransactionNonExclusive() – 在“即时模式”下创建交易。
yieldIfContendedSafely() – 临时占用事务以允许其他线程完成任务。
isDatabaseIntegrityOk() - 检查数据库完整性
更多详情请阅读documentation。
但是,对于旧版本的 Android,此功能也是必需的。
解决方案
应关闭第一个锁定并允许在任何情况下读取数据。
SQLiteDatabase.setLockingEnabled(false);
使用内部查询锁定取消 - 在 java 类的逻辑级别(与 SQLite 中的锁定无关)
SQLiteDatabase.execSQL(“PRAGMA read_uncommitted = true;”);
允许从缓存中读取数据。实际上,更改了隔离级别。应为每个连接重新设置此参数。如果有多个连接,那么它只影响调用此命令的连接。
SQLiteDatabase.execSQL(“PRAGMA 同步=OFF”);
将写入方式更改为数据库——无需“同步”。激活此选项时,如果系统意外故障或电源关闭,数据库可能会损坏。但是根据 SQLite 文档,如果未激活该选项,某些操作的执行速度会快 50 倍。
不幸的是,Android 并不支持所有 PRAGMA,例如不支持“PRAGMAlocking_mode = NORMAL”和“PRAGMA journal_mode = OFF”和其他一些。在尝试调用 PRAGMA 数据时,应用程序失败。
在方法 setLockingEnabled 的文档中,据说只有在您确定所有与数据库的工作都是从单个线程完成的情况下,才建议使用此方法。我们应该保证一次只进行一笔交易。此外,应使用即时事务而不是默认事务(排他事务)。在旧版本的 Android(低于 API 11)中,没有通过 java 包装器创建即时事务的选项,但是 SQLite 支持此功能。要在立即模式下初始化事务,应直接对数据库执行以下 SQLite 查询,例如通过方法 execSQL:
SQLiteDatabase.execSQL(“开始立即事务”);
由于事务是由直接查询初始化的,所以它应该以同样的方式完成:
SQLiteDatabase.execSQL(“提交事务”);
那么 TransactionManager 是唯一需要实现的东西,它将启动和完成所需类型的事务。 TransactionManager 的目的是确保所有更改查询(插入、更新、删除、DDL 查询)都来自同一个线程。
希望这对未来的访客有所帮助!!!