我如何防止SQLite数据库锁?

从sqlite常见问题我已经知道:

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

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

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

最后,当我发现它时,我已经完全混淆了,当指定时:

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

  • 尝试在SELECT语句仍处于待处理状态时创建或删除表或索引。
  • 试图在SELECT在同一个表上处于活动状态时写入表。
  • 试图在多线程应用程序中同时在同一个表上执行两个SELECT,如果没有设置sqlite的话。
  • fcntl(3,F_SETLK对数据库文件的调用失败,例如,这可能是由NFS锁定问题导致的。解决此问题的一个方法是将数据库导出,并将其复制回来,以便它具有新的Inode值
  • 所以,我想澄清一下,我应该什么时候避免锁? 我可以同时从两个不同的线程读写吗? 谢谢。


    不特定于SQLite:

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

    2)获取锁。 但是如果你需要购买多个产品,你必须小心。 对于应用程序级别的每个事务,以一致的(即:按字母顺序?)顺序获取所需的所有资源(锁),以防止在数据库中获取锁时出现死锁。 有时候,如果数据库能够可靠且快速地检测到死锁并抛出异常,你可以忽略它; 在其他系统中,它可能会在没有检测到死锁的情况下挂起 - 因此绝对有必要努力正确获取锁。

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

    一个好的系统应该从一个有效状态转移到另一个有效状态,并且你可以将异常(即使在内存代码中)想象为中止尝试转移到下一个状态; 可以选择忽略或重试。


    对于那些使用Android API的用户

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

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

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

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

    大家都知道SQLiteOpenHelper有两个方法可以分别读写数据,这两个方法提供对数据库getReadableDatabase()getWritableDatabase()的访问。 然而在大多数情况下,有一个真正的联系。 而且它是同一个对象:

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

    这意味着使用数据读取方法没有区别。 然而,还有另一个非文档问题更重要 - 在类SQLiteDatabase内部有自己的锁 - 变量mLock。 在对象SQLiteDatabase的级别上进行锁定,并且由于只有一个SQLiteDatabase副本用于读写,所以数据读取也会被阻止。 在交易中写入大量数据时,它更显着。

    我们来看一个这样的应用程序的例子 ,它应该在首次启动时在后台下载大量数据(大约含有BLOB的7000行)并将其保存到数据库中。 如果数据保存在交易内部,则保存需要约。 45秒,但用户无法使用该应用程序,因为任何阅读查询都被阻止。 如果数据以小部分保存,则更新过程会拖延一段相当长的时间(10-15分钟),但用户可以无任何限制和不便地使用该应用程序。 “双刃剑” - 既快速又方便。

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

    beginTransactionNonExclusive() - 在“IMMEDIATE模式”中创建一个事务。

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

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

    请在文档中详细阅读。

    然而,对于旧版本的Android,此功能也是必需的。

    解决方案

    首先锁定应关闭,并允许在任何情况下读取数据。

    SQLiteDatabase.setLockingEnabled(假);

    取消使用内部查询锁定 - 在java类的逻辑级别(不涉及SQLite方面的锁定)

    SQLiteDatabase.execSQL(“PRAGMA read_uncommitted = true;”);

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

    SQLiteDatabase.execSQL(“PRAGMA synchronous = OFF”);

    将写入方法更改为数据库 - 无需“同步”。 激活此选项时,如果系统意外失效或电源关闭,数据库可能会损坏。 但是,根据SQLite文档,如果该选项未激活,某些操作的执行速度会提高50倍。

    不幸的是,并非所有的PRAGMA都支持Android,例如“ PRAGMA locking_mode = NORMAL ”和“ PRAGMA journal_mode = OFF ”,其他一些不支持。 在尝试调用PRAGMA数据时,应用程序失败。

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

    SQLiteDatabase.execSQL(“begin immediate transaction”);

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

    SQLiteDatabase.execSQL(“commit transaction”);

    然后TransactionManager是唯一需要执行的事情,它将启动并完成所需类型的事务。 TransactionManager的目的是保证所有查询的更改(插入,更新,删除,DDL查询)都源自同一个线程。

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


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

    链接地址: http://www.djcxy.com/p/19763.html

    上一篇: How do I prevent SQLite database locks?

    下一篇: c#