带有内存数据库和SQLiteCPP库的SQLITE_BUSY

问题描述 投票:0回答:1

通过下面这个非常简单的 C++ 程序(从真实的东西简化)我得到:

test: ./SQLiteCpp-3.3.0/src/Database.cpp:92: void SQLite::Database::Deleter::operator()(sqlite3*): Assertion `0 == ret && "database is locked"' failed.
Aborted (core dumped)

当库尝试在

sqlite3_close()
的析构函数中使用
Database
关闭与数据库的连接时,会发生这种情况。

事实上,

ret
的值为SQLITE_BUSY(5)。 SQLITE 文档表明这通常是连接到同一数据库的另一个进程/线程,但是您可以看到这里的情况并非如此。

main.cpp:

#include "nl.hpp"
int main()
{
    auto nl = new NL();
    delete nl;  // Here only to illustrate the problem.
}

nl.hpp:

#include "SQLiteCpp/SQLiteCpp.h"
using namespace std;
class NL
{
public:
    NL();
    unique_ptr<SQLite::Statement> sqlst_insert_net;
    optional<SQLite::Database> db;
    void compile_sql_statements();
    void create_db();

};

nl.cpp:

#include <nl.hpp>

NL::NL() {
    this->create_db();
    this->compile_sql_statements();
}

void NL::create_db() {
    this->db.emplace(SQLite::Database(":memory:", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE));

    this->db->exec(
        "CREATE TABLE net(id INTEGER PRIMARY KEY, name TEXT UNIQUE);"
    );
}

void NL::compile_sql_statements() {
    this->sqlst_insert_net = make_unique<SQLite::Statement>(
        *(this->db), "INSERT INTO net (name) VALUES (?)");
}

这里似乎起作用的一些事情是:

  • 如果我删除
    delete nl;
    ,问题就不会发生。我不是 C++ 专家,但我想
    nl
    对象无论如何都会在
    main()
    结束时被销毁,对吗?
  • 如果我不准备语句,只需从
    this->compile_sql_statements();
    的构造函数中删除
    NL
    就不会出现问题。

以上两件事是必要的。这只是一个非常简化的代码,只是为了复制问题。

NL
的实例在实际程序中保存在
shared_pointer
中,因此最终必然会调用析构函数。并且还需要准备好的陈述。

我之前使用过看似相同的代码,但没有遇到这个问题。

编辑:附加信息。

这是堆栈跟踪:

SQLite::Database::Deleter::operator()(SQLite::Database::Deleter * const this, sqlite3 * apSQLite) (.../SQLiteCpp-3.3.0/src/Database.cpp:92)
std::unique_ptr<sqlite3, SQLite::Database::Deleter>::~unique_ptr(std::unique_ptr<sqlite3, SQLite::Database::Deleter> * const this) (/usr/include/c++/9/bits/unique_ptr.h:292)
SQLite::Database::~Database(SQLite::Database * const this) (.../SQLiteCpp-3.3.0/include/SQLiteCpp/Database.h:264)
std::_Optional_payload_base<SQLite::Database>::_M_destroy(std::_Optional_payload_base<SQLite::Database> * const this) (/usr/include/c++/9/optional:257)
std::_Optional_payload_base<SQLite::Database>::_M_reset(std::_Optional_payload_base<SQLite::Database> * const this) (/usr/include/c++/9/optional:277)
std::_Optional_payload<SQLite::Database, false, false, false>::~_Optional_payload(std::_Optional_payload<SQLite::Database, false, false, false> * const this) (/usr/include/c++/9/optional:398)
std::_Optional_base<SQLite::Database, false, false>::~_Optional_base(std::_Optional_base<SQLite::Database, false, false> * const this) (/usr/include/c++/9/optional:471)
std::optional<SQLite::Database>::~optional(std::optional<SQLite::Database> * const this) (/usr/include/c++/9/optional:656)
NL::~NL(NL * const this) (.../src/nl.hpp:8)
main() (.../src/test.cpp:5)

以及断言失败的函数(来自 SQLiteCpp 的 Database.cpp):

// Deleter functor to use with smart pointers to close the SQLite database connection in an RAII fashion.
void Database::Deleter::operator()(sqlite3* apSQLite)
{
    const int ret = sqlite3_close(apSQLite); // Calling sqlite3_close() with a nullptr argument is a harmless no-op.

    // Avoid unreferenced variable warning when build in release mode
    (void) ret;

    // Only case of error is SQLITE_BUSY: "database is locked" (some statements are not finalized)
    // Never throw an exception in a destructor :
    SQLITECPP_ASSERT(SQLITE_OK == ret, "database is locked");  // See SQLITECPP_ENABLE_ASSERT_HANDLER
}

正如我提到的,

ret==5 (SQLITE_BUSY)

这是包装库:SQLiteC++

编辑:附加信息。

正如评论中所建议的,该语句未正确终止。此处显示的析构函数(其中

sqlite3_finalize(stmt)
所在的位置)从未被调用:

// Prepare SQLite statement object and return shared pointer to this object
Statement::TStatementPtr Statement::prepareStatement()
{
    sqlite3_stmt* statement;
    const int ret = sqlite3_prepare_v2(mpSQLite, mQuery.c_str(), static_cast<int>(mQuery.size()), &statement, nullptr);
    if (SQLITE_OK != ret)
    {
        throw SQLite::Exception(mpSQLite, ret);
    }
    return Statement::TStatementPtr(statement, [](sqlite3_stmt* stmt)
        {
            sqlite3_finalize(stmt);
        });
}

TStatementPtr
是存在于
Statement
中的shared_pointer。
Statement
的析构函数只是默认的。所以看起来
Statement
并没有被破坏。

c++ sqlite
1个回答
0
投票

这个问题恰好很微妙。正如 @Shawn 在问题评论中所建议的,在关闭数据库连接时,准备好的语句尚未最终确定。事实上

Statement
的析构函数应该调用
sqlite3_finalize(stmt)
,但它从未被调用。发生这种情况是因为首先调用了
~Database

这仅仅是因为

Statement
Database
NL
中声明的顺序。这决定了成员被销毁的顺序。

先声明

Database
,然后
Statement
导致
Statement
先被销毁,解决问题。

© www.soinside.com 2019 - 2024. All rights reserved.