【问题标题】:SQLite in C++. DB is BUSY (Multithread)C++ 中的 SQLite。 DB 忙(多线程)
【发布时间】:2015-11-01 18:00:33
【问题描述】:

我有一个问题。我在我的 C++ 项目中使用 SQLite3。在日志中,我有错误:DB 是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();
}

【问题讨论】:

  • 你能分享更多你的代码吗?这肯定有助于缩小错误。
  • 连接是否来自两个不同的线程?还是两个不同的过程?
  • 两个不同的线程。并且这两个连接都是从其他线程中使用的。

标签: c++ multithreading sqlite


【解决方案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 开始,一个新的“预写日志记录”(WAL) 选项 可用。其中读写可以同时进行。 默认情况下,WAL 未启用。开启 WAL,请参考 Sqlite 文档。

【讨论】:

    【解决方案2】:

    您无疑已经意识到,SQLite 一次只允许一个连接来更新数据库。

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

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

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

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

    第二个是利用 SQLite 提供的工具来解决访问数据库时的争用问题。 SQLite 允许你安装一个'busy handler',如果试图访问一个已经被另一个线程或进程锁定的数据库,它将被调用。繁忙的处理程序可以采取任何需要的操作,但最简单的情况通常只是等待一段时间再试一次,这是由内置的繁忙处理程序提供的,您可以通过调用sqlite3_busy_timeout 来安装它。

    例如,在打开数据库连接后,您可以立即执行以下操作:

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

    也可以通过命令设置这样的超时,使用busy_timeout pragma

    您可能还希望考虑使用BEGIN IMMEDIATE TRANSACTIONBEGIN EXCLUSIVE TRANSACTION 开始您的事务,这样可以保证事务在没有阻塞的情况下完成。请参阅documentation on transactions

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-04
      • 1970-01-01
      • 1970-01-01
      • 2010-11-01
      • 2018-07-10
      • 2018-05-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多