如何防止SQLite数据库锁?

时间:2013-06-27 16:29:01

标签: sqlite

来自sqlite FAQ我知道:

  

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

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

但是,我读到Write-Ahead Logging提供了更多的并发性,因为读者不会阻止编写者,而编写者不会阻止读者。阅读和写作可以同时进行。

最后,当我发现it时,我已经完全糊涂了:

  

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

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

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

3 个答案:

答案 0 :(得分:6)

对于使用 Android API 的人:

  

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

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

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

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

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

      

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

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

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

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

    beginTransactionNonExclusive() - 以“IMMEDIATE模式”创建交易。

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

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

    请在documentation

    中详细了解详情

    但是对于旧版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(“commit transaction”);

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

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

答案 1 :(得分:5)

不是特定于SQLite:

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

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

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

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

答案 2 :(得分:1)

你对多线程很好。您链接的页面列出了在同一个帖子中循环显示SELECT的结果(即您的选择处于活动/待处理状态)时无法执行的操作。