【问题标题】:Copying in-memory database to disk crashes on db.close()在 db.close() 上将内存数据库复制到磁盘崩溃
【发布时间】:2023-04-10 18:57:01
【问题描述】:

在 Windows 10 x64 上使用 Qt 5.15.2

我的 sqlite3.h 说

#define SQLITE_VERSION        "3.34.0"
#define SQLITE_VERSION_NUMBER 3034000
#define SQLITE_SOURCE_ID      "2020-12-01 16:14:00 a26b6597e3ae272231b96f9982c3bcc17ddec2f2b6eb4df06a224b91089fed5b"

根据我在本网站上找到的 sqlite3 文档和其他资料,我正在将临时数据库复制到磁盘(:memory: 如果发布,tmp 文件如果调试)。

How to backup/store between sqlite memory database and file database in Qt?

How to access sqlite3 directly from Qt without linking sqlite3.dll a second time

问题是如果数据库超过给定大小,db.close() 行会导致程序崩溃,并显示这个。

数据库是这样创建的。如果我使用 100 而不是 1000,它似乎可以正常工作,不会崩溃。

void SymbolLibDocument::init() {
    if (m_activeState) {
        return;
    } else {
        // Creates temp database to prime save, save as, etc.
        // Does not create anything with full filename
        QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", QString::fromStdString(m_connName));
#if defined(QT_DEBUG)
        db.setDatabaseName("tmp_" + QString::fromStdString(m_name));
#elif defined(QT_NO_DEBUG)
        db.setDatabaseName(":memory:");
#endif
        if(!db.open()) {
            qDebug() << "Can't create database";
        }

        QSqlQuery query(db);
        const
        QStringList qsl = {"DROP TABLE IF EXISTS hello;",
                           "CREATE TABLE hello (ID  INTEGER PRIMARY KEY AUTOINCREMENT, \n"
                           "                    name TEXT CHECK(length(name) > 0));       ",
                           "INSERT INTO hello (name) VALUES ('giraffe');"};
        dbutils::executeList(query, qsl, "Could not init", __LINE__);

        query.exec("BEGIN");
        QString s = QString("INSERT INTO hello (name) VALUES (:v);");
        query.prepare(s);

        for (int i = 0; i < 10000; i++) {
            query.bindValue(":v", QVariant(i));
            query.exec();
        }
        query.exec("COMMIT");
    }
    m_activeState = true;
}


void dbSaveFromTo(const std::string & connFrom, const std::string & fileTo) {
    // Uses sqlite3 backup mechanism to write database connFrom to fileTo

    //auto secs = std::chrono::milliseconds(50);
    QString qsConnFrom = QString::fromStdString(connFrom);
    // Need to clone db so it can be used from this function, which is callable
    // as another thread.
    // Also this needs to go in another scope so db object is destroyed
    // at exit, prior to close and removal
    {
        QSqlDatabase db = QSqlDatabase::cloneDatabase(qsConnFrom, "CloneDb");
        db.open();
        QVariant qvhandle = db.driver()->handle();
        if (qvhandle.isValid() && qstrcmp(qvhandle.typeName(), "sqlite3*") == 0) {
            sqlite3 *pFrom = *static_cast<sqlite3 **>(qvhandle.data());
            sqlite3 *pTo;
            sqlite3_open(fileTo.c_str(), &pTo);
            sqlite3_backup *pBackup = sqlite3_backup_init(pTo, "main", pFrom, "main");
            if (pBackup) {
                int pagesPerCycle = 1;
                int pageCount = 0;
                int pagesCopied = 0;
                int pagesRemaining = 0;
                do {
                    (void) sqlite3_backup_step(pBackup, pagesPerCycle);
                    if (sqlite3_errcode(pFrom) != SQLITE_OK) {
                        qDebug() << sqlite3_errmsg(pFrom);
                    }
                    if (sqlite3_errcode(pTo) != SQLITE_OK) {
                        qDebug() << sqlite3_errmsg(pTo);
                    }
                    pageCount = sqlite3_backup_pagecount(pBackup);
                    pagesRemaining = sqlite3_backup_remaining(pBackup);
                    pagesCopied = pageCount - pagesRemaining;
                    emit intEmitter.emitInt(pagesCopied, pageCount);
                    qDebug() << "emitting" << pagesCopied << "/" << pageCount;
                    //std::this_thread::sleep_for(secs);
                } while(pagesRemaining > 0);
                (void) sqlite3_backup_finish(pBackup);
            } else {
                throw std::logic_error("sqlite3_backup_init(...) failed");
            }
            // causes error 21 bad parameter or other API misuse,
            // But this occurs even if open and close are called back-to-back
            // with no operations in between
            sqlite3_close(pTo);
            if (sqlite3_errcode(pFrom) != SQLITE_OK) {
                qDebug() << sqlite3_errmsg(pFrom);
            }
            if (sqlite3_errcode(pTo) != SQLITE_OK) {
                qDebug() << sqlite3_errmsg(pTo);
            }
        } else {
            throw std::logic_error("invalid driver handle");
        }
        db.close();
    }

    QSqlDatabase::removeDatabase("CloneDb");
}

我担心这是因为我使用了内置的 Qt Sql 功能(.pro 文件中的.pro 中的db.opendb.closeQSqlDatabase::cloneDatabase 等),但是我正在使用单独的 sqlite3 预编译 .dll 来访问 sqlite3_backup_* 函数。

当我执行db.close() 时为什么会崩溃?

【问题讨论】:

  • 我认为问题是在 cloneDatabase 之后你没有检查打开与否
  • 使用单例来避免这个问题
  • 你能否详细说明单例如何避免这个问题?哪个类的单身人士?
  • 正确的做法是不要保留QSqlDatabase成员,而是使用静态函数来访问它
  • 静态函数不能用于我需要它做的事情。我只是对整个事情使用了原始 api,而且效果很好。

标签: sqlite qt


【解决方案1】:

问题确实是用于访问数据库连接的 Qt sqlite 代码与用于初始化和逐步执行备份的 sqlite3.dll 代码之间的一些奇怪的交互。

解决方案是使用 Qt Sqlite 功能仅从连接名称中获取文件名,然后使用原始 sqlite api 打开源文件并进行备份。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-29
    • 1970-01-01
    • 2012-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-06
    相关资源
    最近更新 更多