如何调试被锁定的SQLite3数据库

4
我正在iOS上编写一个应用程序,它使用由fmdatabase封装的sqlite3。 我遇到的问题是,在某些时候,我的程序会陷入循环,进入FMDatabase库中的一个函数内部,特别是调用sqlite3_step并发现数据库正忙的函数,然后一遍又一遍地重试。
我正在寻找一般的调试工具和技巧,因为在此处提供我的整个设置太多了。 有一些可能会产生影响的事情,我打开了一个已经在另一个线程中拥有句柄的数据库句柄。 sqlite3_threadsafe()返回2,所以我知道它已启用。 我还通过制作非常简单的select和update语句来测试了这个新连接。 当我让我的程序运行,并且当它尝试在数据库上运行更新时,我被卡住了。
我的程序自己进行的更新语句没有问题,因为当我不打开两个连接时,此查询可以正常运行。 然而,我无法看出我可能犯了什么错误...
非常感谢任何帮助或提示我可能错的地方。

你提交了更新吗?另请参阅解锁通知方案 - Benoit
fmdatabase 调用了 sqlite3_finalize 但没有提交。我已经提交了,但似乎没有任何区别。 - Ying
1个回答

21

在SQLite进行写操作期间(即当任何一个表正在进行写操作时),整个数据库都会被锁定。有些数据库通过表级锁或行级锁提供并发写入。与SQLite的实现相比,表级锁基本上意味着当您向给定表写入数据时,没有其他线程可以同时写入该表中的任何记录(但在某些情况下,可以同时进行对其他表的写入)。同样,行级锁更进一步,只允许锁定所涉及的必要行,从而允许多个线程同时对同一张表进行并发写入。这里的想法是尽量减少写操作需要锁定的数据量,有效地增加了跨整个数据库的并发写入数量,这取决于您的实现/如何使用数据库,这可以显着提高吞吐量。

现在,回到你的问题...

SQLite是线程安全的并不意味着多个线程可以同时写入它 - 这意味着它有一种处理来自多个线程的访问的方法 - 即(a)允许超时/重试,并且(b)在数据库当前持有锁时返回有用的错误(SQLITE:Busy)。也就是说,线程安全仅仅意味着“多个线程可以以一种不会导致由于并发访问而导致数据损坏的方式访问这些数据”。

基本上,在代码的某个地方,一个线程正在尝试在另一个线程释放数据库锁之前更新它。这是SQLite的常见障碍,因为作者/文档将告诉您,SQLite可以像一位冠军一样处理并发。现实是,SQLite认为“并发支持”的东西相当快,以便对数据库的锁定时间非常短,并且因此在超时之前释放数据库的锁定。在许多情况下,这很好用并且从来没有成为问题。然而,有非常短暂的锁定时间并不意味着可以允许多个线程同时进行写入。

就像iOS实现多任务处理的方式(至少到我写这篇文章时是这样),它实际上是将其他应用程序暂停,然后再回到它们。这样做有两个好处:(a)由于CPU利用率更低,电池寿命更长,(b)您不必每次启动应用程序时都从头开始。这很好,但是在iOS中使用的实际单词“多任务处理”在技术上并不意味着与其他环境中的“多任务处理”相同(即使是Mac OS X也是如此)。

SQLite也是如此。他们是否支持“并发性”?嗯,有点像,但是他们定义“并发性”的方式并不与DB世界中定义的“并发性”相同。

没有人真正错了,但在这些情况下,它会增加实现的混乱。


是的,我不得不用自己的锁来保护数据库,尽管我一直试图避免这样做。 - Ying
2
现在他们有一种叫做WAL的东西。 - Alex
2
写前日志将有助于减轻症状,但仍无法处理多个并发写入的情况。 - jefflunt
1
这是我能在任何地方找到的关于SQLite多线程的最简洁和有用的概述。非常有帮助,谢谢。 - Richard
@Richard - 谢谢。我已经很多年没有使用SQLite了,所以希望这仍然是一个准确反映SQLite实现的答案。 - jefflunt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接