在多线程环境中管理sqlite数据库

Managing sqlite database in a multithreaded environment

本文关键字:sqlite 数据库 管理 多线程 环境      更新时间:2023-10-16

使用sqlite c++库时,我可以使用sqlite3_open_v2打开数据库。这将产生一个数据库句柄,并设置指向该句柄的指针。

使用这个指针,我可以调用sqlite3_prepare_v2来准备一个sqlite语句,然后我可以使用sqlite3_step来遍历查询的结果。

现在,我在一个环境中工作,我有几个多线程,不断地被创建和销毁(它是一个服务器应用程序,产生新的线程来服务传入的,可能并发的连接)。现在,就我的理解而言,我应该在每次创建新线程时调用sqlite3_open_v2,为同一个数据库创建新的句柄。然而,这增加了显著的计算开销,因为创建到数据库的新连接可能需要一段时间,而且我需要处理大量的连接。

所以我想知道是否有更有效的方法来实现这一点。有没有一种方法,比如,让所有东西互斥来解决我的问题?我可以将我的调用互斥到我拥有的唯一连接对象:这将序列化我与数据库的通信。

这个可行吗?或者,即使我避免了任何形式的并发性,为什么我不能在几个不同的线程中使用相同的连接对象?

如果这可以工作,我应该只是序列化我对sqlite3_prepare_v2的调用,或者我对sqlite3_step的第一次调用,或者我对sqlite3_step的所有调用吗?我的意思是:当我第一次调用step时,所有结果都被加载,或者每次调用step时都与实际数据库文件进行通信?

区别在于只对prepare的调用进行互斥,以及锁定所有内容,直到我完成step ping遍历结果。

这样的事情是可行的,我应该只是创建新的连接到数据库,让sqlite处理所有的并发性,或者我错过了一些重要的,琐碎的解决我的问题?

您可以让sqlite3为您处理所有这些,默认情况下它应该这样做。sqlite3库应该默认使用SQLITE_THREADSAFE=1(强调我的):

SQLITE_THREADSAFE=<0 or 1 or 2>
该选项控制是否将代码包含在SQLite中,以使其能够在多线程环境中安全运行。默认为SQLITE_THREADSAFE=1,在多线程环境中使用是安全的。

SQL_CONFIG_SERIALIZED也应该默认使用(强调我的):

SQLITE_CONFIG_SERIALIZED
这个选项没有任何参数。此选项将线程模式设置为Serialized。换句话说,该选项启用所有互斥锁,包括数据库连接和预处理语句对象上的递归互斥锁。在这种模式下(当SQLite使用SQLITE_THREADSAFE=1编译时是默认的) SQLite库将自己序列化对数据库连接和预处理语句的访问,以便应用程序可以自由地同时在不同的线程中使用相同的数据库连接或相同的预处理语句。

然而,你也可以在初始化之前调用sqlite3_config来改变它:

sqlite3_config(SQL_CONFIG_SERIALIZED);

你应该能够打开你的数据库使用SQLITE_OPEN_FULLMUTEX:

sqlite3* pDatabase;
sqlite3_open_v2("MyDatabase.db", &pDatabase, SQLITE_OPEN_FULLMUTEX, nullptr);

你也可以使用std::mutex来防止访问你的sqlite3调用,但这应该不是必要的,因为sqlite3为你处理它(但如果由于某种原因你已经构建了不同的库,这将是可行的)。

我认为你应该检查是否在sqlite3_initialize()之后调用sqlite3_config()函数。如果是,函数sqlite3_config()返回SQLITE_MISUSE。

这是关于sqlite3-config() API的部分解释,涉及错误码SQLITE_MISUSE。

sqlite3_config()接口只能在使用sqlite3_initialize()初始化库之前或在使用sqlite3_shutdown()关闭库之后调用。如果sqlite3_config()sqlite3_initialize()之后,sqlite3_shutdown()之前被调用,那么它将返回SQLITE_MISUSE。注意,sqlite3_config()可以作为应用程序定义的sqlite3_os_init()实现的一部分来调用。

<子> 来源:http://www.sqlite.org/c3ref/config.html