如何在多线程应用程序中使用SQLite?

时间:2009-11-05 12:27:26

标签: c++ multithreading sqlite

我正在开发一个以SQLite作为数据库的应用程序,并且在理解如何在多个线程中使用它时遇到一些麻烦(遗憾的是,没有其他Stack Overflow问题对我有帮助)。

我的用例:数据库有一个表,我们称之为“A”,它有不同的行组(基于其中一列)。我有应用程序的“主线程”,它从表A中读取内容。此外,我偶尔决定更新某一组行。为此,我想生成一个新线程,删除该组的所有行,然后重新插入它们(这是在我的应用程序上下文中执行此操作的唯一方法)。这可能同时发生在不同的组中,因此我可能有2个以上的线程尝试更新数据库。

我正在使用每个线程的不同事务,I.E。在每个线程的更新周期开始时,我有一个开始。实际上,每个线程实际上做的是调用“BEGIN”,从数据库中删除它需要“更新”的所有行,然后使用新值再次插入它们(这是必须在我的上下文中完成的方式)应用程序)。

现在,我正在努力了解如何实现这一目标。我试过阅读(Stack Overflow上的其他答案,SQLite网站),但我还没有找到所有的答案。以下是我想知道的一些事情:

  1. 我是否需要调用“open”并从每个线程创建一个新的sqlite结构?
  2. 我是否需要为所有这些添加任何特殊代码,或者是否足以生成不同的线程,更新行,这很好(因为我正在使用不同的事务)?
  3. 我看到了一些关于不同锁定类型的事情,以及我可能会因调用某些API而收到“SQLite忙”这一事实,但说实话,我没有看到任何完全解释的引用,当我需要采取所有这些时考虑到了。我需要吗?
  4. 如果有人能回答问题/指出我的方向,我会非常感激。

    更新1:从我到目前为止所读到的所有内容来看,似乎你不能有两个线程要写入数据库文件。

    请参阅:http://www.sqlite.org/lockingv3.html。在3.0节中:RESERVED锁意味着该进程计划在将来的某个时刻写入数据库文件,但它当前只是从文件中读取。虽然多个SHARED锁可以与单个RESERVED锁共存,但一次只能激活一个RESERVED锁。

    这是否意味着我每次只能产生一个线程来更新一组行?即,有某种轮询线程决定我需要更新一些行,然后创建一个新线程来执行它,但一次不会超过一个?因为它看起来像我创建的任何其他线程只会获得SQLITE_BUSY,直到第一个线程结束,无论如何。

    我是否理解正确的事情?

    顺便说一句,感谢答案到目前为止,他们帮了很多忙。

7 个答案:

答案 0 :(得分:28)

开始使用SQLlite进行多线程使用时的一些步骤:

  1. 确保使用多线程标志编译sqlite。
  2. 您必须在sqlite文件上调用open以在每个线程上创建连接,不要在线程之间共享连接。
  3. SQLite有一个非常保守的线程模型,当你执行写操作时,包括打开即将执行INSERT / UPDATE / DELETE的事务,其他线程将被阻塞,直到此操作完成。
  4. 如果您不使用事务,那么事务是隐式的,因此如果您启动INSERT / DELETE / UPDATE,sqlite将尝试获取独占锁,并在释放之前完成操作。
  5. 如果您执行BEGIN EXCLUSIVE语句,它将在该事务中执行操作之前获取独占锁。 COMMIT或ROLLBACK将释放锁定。
  6. 您的sqlite3_step,sqlite3_prepare和其他一些调用可能会返回SQLITE_BUSY或SQLITE_LOCKED。 SQLITE_BUSY通常意味着sqlite需要获取锁。两个返回值之间的最大区别:
    • SQLITE_LOCKED:如果你从sqlite3_step语句得到这个,你必须在语句句柄上调用sqlite3_reset。你应该只在第一次调用sqlite3_step时得到这个,所以一旦调用reset,你实际上可以“重试”你的sqlite3_step调用。在其他操作上,它与SQLITE_BUSY
    • 相同
    • SQLITE_BUSY:无需调用sqlite3_reset,只需等待一段时间后重试您的操作即可。

答案 1 :(得分:22)

结帐this link。最简单的方法是自己进行锁定,并避免共享线程之间的连接。可以找到另一个好的资源here,最后得到:

  1. 确保使用-DTHREADSAFE = 1编译SQLite。

  2. 确保每个线程都打开数据库文件并保留自己的sqlite结构。

  3. 确保您处理一个或多个线程同时访问db文件时可能发生冲突的可能性:正确处理SQLITE_BUSY。

  4. 确保在事务中包含修改数据库文件的命令,如INSERT,UPDATE,DELETE等。

答案 2 :(得分:9)

我意识到这是一个老线程并且响应很好但我最近一直在研究这个问题,并且对一些不同的实现进行了有趣的分析。主要是它弥补了连接共享,消息传递,线程本地连接和连接池的优缺点。请在此处查看:http://dev.yorhel.nl/doc/sqlaccess

答案 3 :(得分:3)

Check this code from the SQLite wiki

我做过与C类似的事情,我上传了代码here

我希望它有用。

答案 4 :(得分:1)

SQLite的现代版本默认启用了线程安全性。 SQLITE_THREADSAFE编译标志控制SQLite中是否包含代码,以使其在多线程环境中安全运行。 默认值为SQLITE_THREADSAFE=1。这意味着Serialized mode。在这种模式下:

  

在此模式下(使用SQLITE_THREADSAFE = 1编译SQLite时的默认模式),SQLite库本身将序列化对数据库连接和预准备语句的访问,以便应用程序可以自由使用相同的数据库连接或相同的预准备语句在不同的线程同时。

使用sqlite3_threadsafe()函数检查Sqlite库SQLITE_THREADSAFE编译标志。

可以通过sqlite3_config()更改默认库线程安全行为。使用SQLITE_OPEN_NOMUTEX处的SQLITE_OPEN_FULLMUTEXsqlite3_open_v2()标记来调整各个数据库连接的线程模式。

答案 5 :(得分:0)

<强>摘要

SQLite中的事务是SERIALIZABLE。

在提交之前,对一个数据库连接所做的更改对于所有其他数据库连接是不可见的。

查询会查看在查询开始之前在同一数据库连接上完成的所有更改,无论这些更改是否已提交。

如果在查询开始运行之后但在查询完成之前在同一数据库连接上发生更改,则查询是否会看到这些更改是不确定的。

如果在查询开始运行之后但在查询完成之前在同一数据库连接上发生更改,则查询可能会多次返回已更改的行,或者它可能会返回先前已删除的行。

出于前四个项目的目的,使用相同共享缓存并启用PRAGMA read_uncommitted的两个数据库连接被视为相同的数据库连接,而不是单独的数据库连接。

除了上面有关多线程访问的信息之外,可能值得一看this page on isolation,因为自这个原始问题以及引入预写日志( WAL )。

似乎是一种混合方法,可以向数据库打开几个连接,提供足够的并发保证,以牺牲打开新连接为代价,并允许多线程写入事务。

答案 6 :(得分:0)

如果使用连接池(例如在Java EE,Web应用程序中),请设置最大连接池。大小为1。访问权限将被序列化。