C ++中的SQLite。 DB是BUSY(多线程)

时间:2015-08-09 21:40:36

标签: c++ multithreading sqlite

我遇到了问题。我在我的C ++项目中使用SQLite3。在日志中,我遇到了错误:数据库为locked error code 5。据我所知,错误代码5表示数据库正忙。为了解决这个问题,我开始使用WAL日志模式。但它没有帮助。

在我的程序中,我有2个连接到同一个数据库。我将互斥锁用于两个数据库连接。 我正在使用此代码打开连接:

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);
}

第一个连接用于将数据插入数据库。它每秒发生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();
}

2 个答案:

答案 0 :(得分:1)

请检查这两个Stack Overflow帖子。它们似乎与您的问题有关。

Can different connections of the same sqlite's database begin transactions concurrently?

  

如果您阅读了SQLite文档,您将看到它支持   多个连接只供读取,你不能写入   来自多个连接的数据库,因为它不是为此而设计的   这一点。

Read and Write Sqlite database data concurrently from multiple connections

  

多个进程可以同时打开相同的sqlite数据库   时间,可以并行满足几个读访问。

     

在写入的情况下,对数据库的单次写入会锁定   数据库很短的时间,没什么,甚至是阅读,都可以访问   数据库文件。

     

从版本3.7.0开始,新的“Write Ahead Logging”(WAL)选项   是可用的。阅读和写作可以同时进行。   默认情况下,WAL未启用。要打开WAL,请参阅Sqlite   文档。

答案 1 :(得分:1)

毫无疑问,SQLite一次只允许一个连接更新数据库。

从您粘贴的代码中,您看起来好像有两个独立的互斥锁,一个用于readings_collector实例,另一个用于dataSend_task实例。这些可以防止两个函数中的每个函数的多次执行,但不会同时针对这两个函数运行。

从您的问题中不清楚互斥体的用途是什么,但它肯定不会阻止这两个连接同时尝试更新数据库。

我可以建议两种方法来解决你的问题。

第一种方法是在这两个实例之间使用单个共享互斥锁,这样一次只能有一个实例更新数据库。

第二种方法是利用SQLite提供的功能来解决访问数据库时的争用问题。 SQLite允许您安装“busy handler”,如果尝试访问已被另一个线程或进程锁定的数据库,将调用该“sqlite3_busy_timeout”。繁忙的处理程序可以采取任何所需的操作,但最简单的情况通常只是等待一段时间再试一次,这可以通过调用busy_timeout pragma安装的内置繁忙处理程序来满足。

例如,在打开数据库连接后立即执行此操作:

sqlite3_busy_timeout(this->db, 1000); // Wait 1000mS if busy

也可以使用documentation on transactions按命令设置此类超时。

您可能还希望考虑使用BEGIN IMMEDIATE TRANSACTIONBEGIN EXCLUSIVE TRANSACTION启动交易,以确保交易完成而不会阻止。请参阅http://angulargrid.com

相关问题