如何防止SQLite数据库锁?

And*_*lov 10 sqlite

从sqlite FAQ我知道:

多个进程可以同时打开同一个数据库.多个进程可以同时执行SELECT.但是,只有一个进程可以随时对数据库进行更改.

所以,据我所知,我可以:1)从多个线程读取数据库(SELECT)2)从多个线程读取数据库(SELECT)并从单个线程写入(CREATE,INSERT,DELETE)

但是,我读到了日志,它提供了更多的并发性,因为读者不会阻止编写者,而编写者也不会阻止读者.阅读和写作可以同时进行.

最后,当我找到时,我已经完全糊涂了:

以下是获取SQLITE_LOCKED错误的其他原因:

  • 在SELECT语句仍处于挂起状态时尝试创建或删除表或索引.
  • 当SELECT在同一个表上处于活动状态时,尝试写入表.
  • 如果未设置sqlite,则尝试在多线程应用程序中同时在同一个表上执行两个SELECT.
  • fcntl(3,对数据库文件的F_SETLK调用失败.这可能是由于NFS锁定问题引起的.例如,针对此问题的一个解决方案是将数据库移除,然后将其复制回来以使其具有新的Inode值

那么,我想为自己澄清什么时候应该避免锁?我可以从两个不同的线程同时读写吗?谢谢.

PN1*_*N10 6

对于使用Android API的用户:

在SQLite中锁定是在文件级别完成的,这可以保证锁定来自不同线程和连接的更改.因此,多个线程可以读取数据库,但是只能写入数据库.

有关SQLite锁定的更多信息可以在SQLite文档中阅读,但我们最感兴趣的是OS Android提供的API.

可以从单个和多个数据库连接进行两个并发线程的写入.由于只有一个线程可以写入数据库,因此有两种变体:

  1. 如果你从一个连接的两个线程写入,那么一个线程将等待另一个线程完成写入.
  2. 如果从两个不同连接的线程写入,则会出现错误 - 所有数据都不会写入数据库,应用程序将被SQLiteDatabaseLockedException中断.很明显,应用程序应始终只有一个SQLiteOpenHelper副本(只是一个打开的连接),否则SQLiteDatabaseLockedException可以随时发生.

单个SQLiteOpenHelper上的不同连接

每个人都知道SQLiteOpenHelper有两个方法可以访问数据库getReadableDatabase()getWritableDatabase(),分别读取和写入数据.但是在大多数情况下,存在一个真正的连接.而且它是同一个对象:

SQLiteOpenHelper.getReadableDatabase()== SQLiteOpenHelper.getWritableDatabase()

这意味着使用从中读取数据的方法没有区别.然而,还有另一个未记录的问题更为重要 - 在类SQLiteDatabase中有自己的锁 - 变量mLock.锁定用于在对象SQLiteDatabase的级别进行写入,并且由于只有一个SQLiteDatabase副本用于读取和写入,因此也会阻止数据读取.在事务中编写大量数据时,它更加突出.

让我们考虑这样一个应用程序的示例,它应该在首次启动时在后台下载大量数据(大约7000行包含BLOB)并将其保存到数据库中.如果数据保存在事务中,则保存大约需要.45秒但用户无法使用该应用程序,因为任何读取查询都被阻止.如果数据以小部分保存,则更新过程拖出相当长的时间(10-15分钟),但用户可以使用该应用程序而没有任何限制和不便."双刃剑" - 快速或方便.

Google已经修复了与SQLiteDatabase功能相关的部分问题,因为添加了以下方法:

beginTransactionNonExclusive() - 以"IMMEDIATE模式"创建事务.

yieldIfContendedSafely() - 临时占用事务以允许其他线程完成任务.

isDatabaseIntegrityOk() - 检查数据库完整性

请在文档中阅读更多详细信息.

但是对于旧版Android,此功能也是必需的.

解决方案

应关闭第一次锁定并允许在任何情况下读取数据.

SQLiteDatabase.setLockingEnabled(假);

取消使用内部查询锁定 - 在java类的逻辑级别上(与SQLite的锁定无关)

SQLiteDatabase.execSQL("PRAGMA read_uncommitted = true;");

允许从缓存中读取数据.实际上,改变了隔离的程度.应该为每个连接重新设置此参数.如果存在多个连接,则它仅影响调用此命令的连接.

SQLiteDatabase.execSQL("PRAGMA synchronous = OFF");

将写入方法更改为数据库 - 没有"同步".激活此选项时,如果系统意外失败或电源关闭,则可能会损坏数据库.但是,根据SQLite文档,如果未激活该选项,某些操作的执行速度会快50倍.

遗憾的是,Android 并不支持所有PRAGMA,例如" PRAGMA locking_mode = NORMAL "和" PRAGMA journal_mode = OFF ",而其他一些不受支持.在尝试调用PRAGMA数据时,应用程序失败.

在方法setLockingEnabled的文档中,据说只有在您确定所有数据库工作都是从单个线程完成的情况下才建议使用此方法.我们应该保证比一次只举行一次交易.而不是默认事务(独占事务),应使用立即事务.在旧版本的Android(API 11以下)中,没有选项通过java包装器创建立即事务,但SQLite支持此功能.要在立即模式下初始化事务,应该直接对数据库执行以下SQLite查询,例如通过方法execSQL:

SQLiteDatabase.execSQL("立即开始事务");

由于事务是由直接查询初始化的,因此它应该以相同的方式完成:

SQLiteDatabase.execSQL("提交事务");

然后,TransactionManager是唯一可以启动和完成所需类型的事务的东西.TransactionManager的目的是保证所有更改查询(插入,更新,删除,DDL查询)都来自同一个线程.

希望这有助于未来的访客!


Rob*_*Rob 5

不是特定于SQLite:

1)编写代码以优雅地处理在应用程序级别发生锁定冲突的情况; 即使你编写了代码,这是"不可能的".使用事务重试(即:SQLITE_LOCKED可能是您解释为"再试一次"或"等待再试一次"的许多代码之一),并将其与应用程序级代码进行协调.如果你考虑一下,获得一个SQLITE_LOCKED比单纯尝试挂起更好,因为它被锁定了 - 因为你可以去做别的事情.

2)获取锁.但是如果你需要获得不止一个,你必须要小心.对于应用程序级别的每个事务,以一致(即:按字母顺序排列?)顺序获取所需的所有资源(锁定),以防止在数据库中获取锁定时出现死锁.如果数据库可靠且快速地检测到死锁并抛出异常,有时您可以忽略它; 在其他系统中,它可能只是在没有检测到死锁的情况下挂起 - 这使得必须花费精力来正确获取锁.

除了锁定生活的事实之外,您应该尝试从一开始就设计数据和内存结构,并同时进行合并和回滚.如果您可以设计数据使得数据争用的结果为所有订单提供了良好的结果,那么在这种情况下您不必处理锁.一个很好的例子是在不知道其当前值的情况下递增计数器,而不是读取值并提交要更新的新值.它类似于附加到一个集合(即:添加一行,这样行插入的顺序无关紧要).

一个好的系统应该在事务上从一个有效状态移动到另一个有效状态,并且你可以将异常(甚至在内存代码中)视为中止尝试移动到下一个状态; 可以选择忽略或重试.


rec*_*que 1

你对多线程没问题。SELECT您链接的页面列出了在同一线程中循环结果(即您的选择处于活动/待定状态)时不能执行的操作。