SQLite的进程间线程安全性(在iOS上)

10

我正在尝试确定在iOS上访问sqlite数据库是否是线程安全的。我正在编写一个非App Store应用程序(或可能是启动守护程序),因此苹果的批准不是问题。所涉及的数据库是内置的 sms.db ,因此可以确定操作系统也会访问该数据库进行读取和写入。我只想能够安全地读取它。

我已经阅读了关于使用sqlite从多个进程中读取的内容

多个进程可以同时打开同一个数据库。 多个进程可以同时执行SELECT语句。但是,在任何时刻只有一个进程可以对数据库进行更改。

我知道SQLite可以编译出线程安全功能,并且可以使用sqlite3_threadsafe()进行测试。在iOS 5.0.1上运行此代码:

int safe = sqlite3_threadsafe();
结果如下:

执行结果为2。 根据这个,这意味着互斥锁定是可用的。但是,这并不一定意味着它正在使用中。

我不太清楚线程安全性是在每个连接、每个数据库还是全局基础上动态启用的。

我也读了这个。看起来sqlite3_config()可以用于启用安全的多线程,但当然,我没有控制,也没有可见性来了解操作系统本身可能如何使用此调用(对吗?)。如果我在我的应用程序中再次进行该调用,那么它是否会使读取数据库变得安全,还是仅为使用相同的sqlite3数据库句柄的多个线程在我的应用程序中消除冲突访问?

无论如何,我的问题是...

我能够安全地读取由iOS访问的这个数据库吗?如果可以,怎样才能做到安全?

2个回答

12

我从未使用过SQLite,但我花了相当多的时间阅读它的文档,因为我计划在将来使用它(而且这些文档很有趣)。我认为线程安全与多个进程是否可以同时访问同一数据库文件是独立的。无论SQLite处于什么线程模式下,它都会锁定数据库文件,以便多个进程可以同时从数据库中读取但只能写入一个。

线程安全性只影响进程如何使用SQLite。没有任何线程安全性,您只能从一个线程调用SQLite函数。但是,在写入之前,它应该仍然获取互斥锁,以使其他进程无法损坏数据库文件。线程安全只是保护您进程内存中的数据不被多个线程损坏。因此,我认为您永远不需要担心另一个进程(在此情况下是iOS)对SQLite数据库正在做什么。

编辑:为了澄清,每次写入数据库,包括普通的INSERT/UPDATE/DELETE,都会自动获取互斥锁,写入数据库,然后释放锁。(实际上会先获取共享锁,然后是保留锁、挂起锁,最后才是互斥锁。)默认情况下,如果数据库已被锁定(比如来自另一个进程),那么SQLite将返回SQLITE_BUSY而不等待。您可以调用sqlite3_busy_timeout()来告诉它等待更长的时间。


当你说“在写入之前,它仍然应该获取一个独占锁”,你是指SQLite会自动为我执行此操作吗?还是我(或iOS SMS框架)需要负责这个?例如,在写入之前,我(或iOS)是否需要发出BEGIN EXCLUSIVE TRANSACTION;语句? - Nate
我添加了一个编辑。 BEGIN EXCLUSIVE TRANSACTION 用于在事务开始时获取排他锁。默认情况下,SQLite 尽可能长地等待。根据文档:请注意,BEGIN 命令不会在数据库上获取任何锁。在 BEGIN 命令之后,第一次执行 SELECT 语句将获取共享锁。在第一次执行 INSERT、UPDATE 或 DELETE 语句时将获取保留锁。... - Jordan Miner
直到内存缓存填满并必须溢出到磁盘,或者事务提交之前,都不会获取任何排他锁。通过这种方式,系统将阻止对文件的读取访问,直到最后一刻。此外,即使您没有使用“BEGIN TRANSACTION”,所有语句仍处于隐式事务中。 - Jordan Miner
从文档中得知:隐式事务(自动启动的事务,而不是由BEGIN启动的事务)在最后一个活动语句完成时自动提交。 因此,您不需要为事务使用BEGIN。您可以将普通的INSERT语句传递给sqlite3_exec(),它将作为隐式事务运行。但是,这个答案可能只是建议使用BEGIN IMMEDIATE而不是仅仅使用BEGIN。这会使您的代码更清晰,但您并不一定要这样做。... - Jordan Miner
如果您使用BEGIN IMMEDIATE并且数据库被另一个进程锁定,SQLite将在事务开始时返回SQLITE_BUSY。然后您可以再次尝试。如果您使用BEGIN并且数据库被锁定,那么SQLite将在稍后的某个时间返回SQLITE_BUSY。这可能是当您尝试提交时,也可能是更早的语句。如果是更早的语句,则应回滚事务。如果是在尝试提交时,事务仍然处于活动状态,您可以尝试再次提交。因此,与BEGIN IMMEDIATE相比,需要更多的逻辑处理。 - Jordan Miner
显示剩余3条评论

2

我认为这些对你来说都不是新闻,但还有一些想法:

在启用多线程(串行或多线程)方面,通常的建议是可以调用sqlite3_config()(但你可能需要像文档中建议的那样先关闭,或者像这里讨论的那样),以启用所需的多线程。然而,在这里,你无法控制iOS请求sqlite和/或此数据库的访问方式,因此这种方法可能不太有用。

因此,从学术角度来看,读取此系统数据库可能并不安全(因为如你所说,你无法保证操作系统正在做什么)。但从更实际的角度来看,我不会感到惊讶,如果iOS使用任何默认模式打开数据库,那么你可能会没问题。

显然,对于大多数关心单个应用程序内多线程访问的用户,最好的建议是绕过sqlite3_config()的愚蠢,只需通过自己的GCD串行队列确保协调访问(即,拥有一个专用队列,通过该队列进行所有数据库交互,从而完美地消除多线程问题)。不幸的是,在这里这不是一个选择,因为你正在尝试协调与iOS本身的数据库交互。


感谢您周到且写得很好的回复。首先,关于关闭... sqlite3_shutdown() 是否会关闭整个操作系统中的 SQLite,因为如果是这样的话,那似乎也是另一个进程间线程安全问题。我猜想一个不安全的操作可能比每次想读取 .db 时都做一些不安全的操作要好,但也许并非如此? - Nate
其次,GCD队列似乎是协调我自己的多个线程之间访问的好方法,但这不是我的问题(实际上,我的应用程序可能只会为SQL操作使用一个线程)。我该如何强制iOS也使用这些队列?或者我有什么遗漏的吗?(通常,当我问这个问题时,答案是“是的”!) - Nate
@Nate 关于您的第一个问题,sqlite3_shutdown(),我无法回答。我只是引用那些其他参考资料。我认为这不是一个好主意,但我不能确定。 - Rob
完全由您决定,但您是否考虑删除您的答案?听起来您理解我的问题与实际不同。并非您提供的信息有误,而是问题不同。我知道如果看到问题当前“未回答”,我个人更有可能回答问题,而不是一些懒惰的提问者不想点击“接受”按钮。无论如何,这取决于您,只是想问一下,因为我还没有得到太多回应。 - Nate
@Nate 我已经修改了我的答案,重点关注跨进程数据库互操作的关键问题。尽管我知道我没有回答你的问题(不确定如何访问Apple没有提供接口的系统数据库是否有好的答案),但我认为承认多线程sqlite的两种常见方法,例如sqlite3_config()和GCD串行队列,是有价值的,因为人们可能会在寻找标准的多线程sqlite访问问题时偶然发现你的问题。但是,如果你坚决要求我删除我的答案,我愿意这样做。请告诉我。 - Rob
显示剩余5条评论

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