c++中的SQLite.数据库是BUSY(多线程)
SQLite in C++. DB is BUSY (Multithread)
我遇到了一个问题。我在我的c++项目中使用SQLite3。在日志中,我有错误:DB是locked error code 5
。据我所知,错误码5表示DB忙。为了解决这个问题,我开始使用WAL日志模式。但这没有用。
在我的程序中,我有两个连接到同一个DB。我对两个DB连接都使用互斥锁。我用下面的代码打开连接:
if (sqlite3_open_v2(db_path.c_str(), &this->db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, 0) ) {
LOG4CPLUS_FATAL(this->logger, "Can not open/create DB " << sqlite3_errmsg(db));
sqlite3_close(this->db);
}
if (sqlite3_exec(this->db, "PRAGMA journal_mode = WAL;", 0, 0, &err)) {
LOG4CPLUS_ERROR(this->logger, "SQL det journal mode error: " << err);
sqlite3_free(err);
}
第一个连接用于向DB插入数据。它每秒发生4次。
第二个连接用于启动事务、选择、更新、删除数据和提交。每5秒发生一次。
第一个连接出现错误。
请帮我解决这个问题。
更新:
第一次连接:
void readings_collector::flushToDb()
{
this->db_mutex.lock();
LOG4CPLUS_DEBUG(this->logger, "Flush to DB start.");
const char *query = "INSERT INTO `readings` (`sensor_id`, `value`, `error`, `timestamp`) VALUES (?,?,?,?)";
sqlite3_stmt *stmt = NULL;
int rc = sqlite3_prepare_v2(this->db, query, -1, &stmt, NULL);
if (SQLITE_OK != rc) {
LOG4CPLUS_ERROR(this->logger, "sqlite prepare insert statment error: " << sqlite3_errmsg(this->db));
}
LOG4CPLUS_TRACE(this->logger, "--------------------");
LOG4CPLUS_TRACE(this->logger, this->readings.size());
while(!this->readings.empty()) {
sensor::reading temp_reading = this->readings.front();
this->readings.pop();
LOG4CPLUS_TRACE(this->logger, "Reading " << temp_reading.sensor_id << " : " << temp_reading.value << " : " << temp_reading.error << " : " << temp_reading.timestamp);
sqlite3_clear_bindings(stmt);
sqlite3_bind_int(stmt, 1, temp_reading.sensor_id);
sqlite3_bind_text(stmt, 2, temp_reading.value.c_str(), sizeof(temp_reading.value.c_str()), NULL);
sqlite3_bind_int(stmt, 3, temp_reading.error);
sqlite3_bind_int(stmt, 4, temp_reading.timestamp);
rc = sqlite3_step(stmt);
if (SQLITE_DONE != rc) {
LOG4CPLUS_ERROR(this->logger, "sqlite insert statment exec error: " << sqlite3_errmsg(this->db) << "; status: " << rc);
}
}
sqlite3_finalize(stmt);
LOG4CPLUS_TRACE(this->logger, "Flush to DB finish.");
this->db_mutex.unlock();
}
第二连接:
void dataSend_task::sendData()
{
this->db_mutex.lock();
char *err = 0;
LOG4CPLUS_INFO(this->logger, "Send data function");
if (sqlite3_exec(this->db, "BEGIN TRANSACTION", 0, 0, &err)) {
LOG4CPLUS_ERROR(this->logger, "SQL exec error: " << err);
sqlite3_free(err);
}
if (sqlite3_exec(this->db, this->SQL_UPDATE_READINGS_QUERY, 0, 0, &err)) {
LOG4CPLUS_ERROR(this->logger, "SQL exec error: " << err);
sqlite3_free(err);
}
this->json.clear();
this->readingsCounter = 0;
if (sqlite3_exec(this->db, this->SQL_SELECT_READINGS_QUERY, +[](void *instance, int x, char **y, char **z) {
return static_cast<dataSend_task *>(instance)->callback(0, x, y, z);
}, this, &err)) {
LOG4CPLUS_ERROR(this->logger, "SQL exec error: " << err);
sqlite3_free(err);
} else {
LOG4CPLUS_TRACE(this->logger, "Json data: " << this->json);
if (this->curlSend()) {
if (sqlite3_exec(this->db, this->SQL_DELETE_READINGS_QUERY, 0, 0, &err)) {
LOG4CPLUS_ERROR(this->logger, "SQL exec error: " << err);
sqlite3_free(err);
}
}
}
if (sqlite3_exec(this->db, "COMMIT", 0, 0, &err)) {
LOG4CPLUS_ERROR(this->logger, "SQL exec error: " << err);
sqlite3_free(err);
}
this->db_mutex.unlock();
this->json.clear();
}
您无疑已经意识到,SQLite一次只允许一个连接更新数据库。
从您粘贴的代码中,看起来好像您有两个单独的互斥体,一个用于readings_collector
实例,另一个用于dataSend_task
实例。这可以防止两个函数中的每个函数多次执行,但不能防止两个函数同时运行。
从你的问题中我不清楚互斥锁的目的是什么,但它肯定不会阻止两个连接同时尝试更新数据库。
我可以建议两个方法来解决你的问题。
第一种方法是在两个实例之间使用一个共享互斥锁,这样一次只有一个实例可以更新数据库。
第二种方法是利用SQLite在访问数据库时提供的解决争用的功能。SQLite允许您安装一个"繁忙处理程序",它将在尝试访问已被另一个线程或进程锁定的数据库时被调用。繁忙处理程序可以采取所需的任何操作,但最简单的情况通常只是等待一段时间然后再试一次,这是由内置的繁忙处理程序提供的,您可以通过调用sqlite3_busy_timeout
来安装该处理程序。
例如,在打开数据库连接后,您可以这样做:
sqlite3_busy_timeout(this->db, 1000); // Wait 1000mS if busy
也可以通过命令设置这样的超时,使用busy_timeout pragma。
你也可以考虑使用BEGIN IMMEDIATE TRANSACTION
或BEGIN EXCLUSIVE TRANSACTION
启动你的事务,这样可以保证事务在没有阻塞的情况下完成。
请查看这两个Stack Overflow帖子。它们似乎与你的问题有关。
同一个sqlite's数据库的不同连接可以并发开始事务吗?
如果你阅读SQLite文档,你会看到它支持多个只读连接,不能写入数据库从多个连接,因为它不是为
。
从多个连接并发读写Sqlite数据库数据
多个进程可以同时打开同一个sqlite数据库在时间上,可以同时满足多个读访问。
在写操作的情况下,对数据库的单次写操作将锁定数据库短时间内,什么都没有,即使是阅读,也可以访问数据库文件。
从3.7.0版本开始,一个新的"预写日志记录"(WAL)选项是可用的。读和写可以同时进行。缺省情况下,未启用WAL功能。要打开WAL,请参考Sqlite文档。
- 在C++中使用cURL和多线程
- 多线程双缓冲区
- 为什么我的多线程作业队列崩溃
- 在main()之外初始化std::vector会导致性能下降(多线程)
- 试图创建一个多线程程序来查找0-100000000之间的总素数
- 为什么一个向量上的多线程操作很慢
- 学习多线程C++:添加线程不会使执行速度更快,即使它看起来应该
- 全局变量 多读取器 一个写入器多线程安全?
- boost::文件系统::recursive_directory_iterator多线程安全
- 如何阻止TensorFlow的多线程
- 如何在多线程中正确使用unique_ptr进行多态性?
- 并发/多线程:是否可以以这种方式生成相同的输出?
- sigwait() 在多线程程序中不起作用
- 多线程程序中出现意外的内存泄漏
- 静态 constexpr 类成员变量对多线程读取是否安全?
- 多线程比没有线程C++慢
- 具有 C++11 多线程的特征库
- 通过安装信号处理程序关闭多线程应用程序
- 成员变量在多线程 C++ 时自行更改
- c++中的SQLite.数据库是BUSY(多线程)