我正在iOS上编写一个使用fmdatabase包装的sqlite3的应用程序。我遇到的问题是,在某些时候,我的程序卡在FMDatabase库的循环中,特别是一个调用sqlite3_step的函数,发现数据库正忙,然后一遍又一遍地重试。
我正在寻找一般的调试工具和技巧,因为在这里完成整个设置太过分了。有些事情可能是重要的,我打开了一个数据库句柄,数据库句柄已经在另一个线程中有一个句柄。 sqlite3_threadsafe()返回2,所以我知道它已启用。我还通过一个非常简单的select和update语句测试了这个新连接。当我让程序运行时,当它试图在数据库上运行更新时,我就会卡住。
我的程序自身的更新语句没有错,因为当我不打开两个连接时,此查询运行正常。然而,我没有看到我可能出错的地方......
我将非常感谢任何有关我可能出错的帮助或提示。
答案 0 :(得分:21)
SQLite在写入操作期间锁定整个数据库(即,当任何表上发生写入时,没有其他写入,任何地方的任何表都可能同时发生)。某些数据库通过表级锁或有时行级锁提供并发写入。为了与SQLite的实现形成对比,表级锁基本上意味着当您将数据写入给定表时,没有其他线程可以同时写入该表中的任何记录(但是,写入其他表可以在某些情况下同时发生)。类似地,行级锁甚至更进一步,并且仅允许锁定所涉及的必要行,从而允许从多个线程发生对同一表的并发写入。这里的想法是最小化锁定写入操作所需的数据量,这有效地增加了数据库中跨的并发写入量,并取决于您的实现/如何使用数据库,这可以显着提高吞吐量。
现在,回到你的问题......
SQLite是线程安全的事实并不意味着多个线程可以同时写入它 - 这意味着它有一种处理来自多个线程的访问的方式 - 这是(a)允许超时/重试,以及(b) )当数据库上当前存在锁时返回有用的错误(SQLITE:Busy)。也就是说, threadsafe 仅仅意味着“多个线程可以以不会因同时访问而导致数据损坏的方式访问此数据。”
基本上,在代码中的某个地方,一个线程试图在另一个线程释放其对数据库的锁之前进行更新。这是SQLite的常见障碍,因为作者/文档会告诉您SQLite可以像冠军一样处理并发。实际情况是,SQLite认为“并发支持”相当于试图非常快,因此数据库上的锁只能保持很短的时间,因此在超时之前释放数据库上的锁。在很多情况下,这很好用,永远不会妨碍你。但是,拥有非常短暂的锁与实际允许来自多个线程的并发写入不一样。
将其视为iOS执行多任务的方式(至少从iOS 5开始,当我写这篇文章时) - 它真正做的是暂停其他应用程序并回归它们。这样做的结果是:(a)由于CPU利用率较低,电池寿命会更好;(b)每次启动时都不必从头开始启动应用程序。这很棒,但iOS中使用的实际“多任务”一词在技术上与其他环境(甚至Mac OS X)中的“多任务处理”并不相同。
SQLite也是一样的。他们有“并发”支持吗?好吧,但他们定义“并发”这个词的方式并不是DB世界的其他部分定义“并发”的方式。
没有人真的错了,但在这种情况下,它会增加实施的混乱。