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