【问题标题】:qt simultaneous MySQL queries in different threads = crashqt 在不同线程中同时查询 MySQL = 崩溃
【发布时间】:2014-04-21 16:23:02
【问题描述】:

我有一个用 C++ 编写的多线程 Qt5 应用程序,其中每个线程都写入同一个数据库。

我跨线程共享同一个数据库 QSqlDatabase 变量,但每个线程创建自己的查询。我经常遇到崩溃,并且我在崩溃时捕获了这些信息:

Driver error [QMYSQL3: Unable to store statement results]
Database error [Commands out of sync; you can't run this command now]
Type [2]
Number [2014]

首先,我可以在多个线程中同时进行 MySQL 查询吗?如果是这样,我是否需要使用互斥锁来保护 SQL 调用?还是我需要发布更多信息....


更新:我有一个单独的 DBManager 线程来处理打开/关闭数据库和简单的数据库写入。我这样做是因为我的数据库经常脱机,并且我不希望其他线程在数据库打开失败时挂起长达 2 分钟。不,我有更多执行报告的线程,并且必须从数据库中检索大量数据。

如下所述,不允许跨线程共享数据库句柄。所以现在也许这更像是一个设计问题——处理这种情况的最佳方法是什么?我不希望每个执行数据库访问的线程都尝试打开它并等待 2 分钟以防数据库脱机

【问题讨论】:

    标签: c++ mysql multithreading qt


    【解决方案1】:

    阅读 SQL 模块的文档,特别是 threads section,它声明每个连接只能在创建它的线程中使用。

    因此,添加互斥锁还不够好,您必须在每个线程中创建一个新的连接对象,或者将所需的数据传入/传出执行所有数据库查询的专用线程。

    【讨论】:

    • 我曾想过让一个数据库线程执行所有查询,但我将如何以一种方式传递查询并返回结果? (DB 结果的信号和槽?)
    • 任何你想要的信号/插槽方式听起来都不错,但你不得不担心大量的数据传递。我更担心结果记录集很难编组到其他线程,因此您必须将记录集数据复制到中性数据结构中才能传递。为每个线程创建一个新的数据库连接可能要容易得多。测试看看它有多昂贵,因为无论如何都有许多数据库驱动程序池连接。
    • 我的问题是数据库有时会离线,所以我有一个单独的线程,它会每隔一段时间尝试重新打开数据库。只有当它成功时,其他线程才会尝试进行 DB 访问——通过共享 QSqlDatabase 指针(如果非零)。我害怕尝试打开一个新连接,这可能会在打开失败时将我的 GUI 线程挂起 2 分钟
    • 为什么单独线程中的连接会挂起您的 GUI 线程?
    【解决方案2】:

    我通常通过在我的数据库管理器类上提供一个工厂方法来处理这个问题,看起来类似于这个(半伪代码):

    static QHash<QThread, QSqlDatabase> DatabaseManager::s_instances;
    static QMutex DatabaseManager::s_databaseMutex;
    QSqlDatabase DatabaseManager::database() {
        QMutexLocker locker(s_databaseMutex);
        QThread *thread = QThread::currentThread();
    
        // if we have a connection for this thread, return it
        if (s_instances.contains(thread))
            return s_instances[thread];
        }
    
        // otherwise, create a new connection for this thread
        QSqlDatabase connection =
            QSqlDatabase::cloneDatabase(existingConnection, uniqueNameForNewInstanceOnThisThread);
    
        // open the database connection
        // initialize the database connection
    
        s_instances.insert(thread, connection);
        return connection;
    }
    

    只要您确保创建了初始连接(您可以修改工厂以包含它,或者只是在 DatabaseManager 构造函数中创建初始连接),您应该能够替换您正在使用的大多数现有案例一个 QSqlDatabase:

    QSqlQuery query(DatabaseManager::database());
    query.exec(<some sql>);
    

    不用担心您从哪个线程调用。希望对您有所帮助!

    【讨论】:

    • 有趣 - 但不确定这有什么帮助。由于每个线程都有自己的连接,所以您不是白白增加了复杂性吗?为什么不让每个线程打开自己的连接? (我怀疑有一个我不明白的好处)
    • 正如 gbjbaanb 上面指出的那样,每个线程都有一个单独的连接是硬性要求,因此必须在某个地方隔离这种复杂性。虽然您是正确的,您可以在创建线程的地方创建一个新连接,但我发现这种方法更优雅 a) 它以惰性方式创建连接(在需要时创建它们,而不是前端加载大量的连接),b)您再也不必考虑数据库线程问题,让您专注于您正在尝试做的实际工作
    • 嗨@mbroadst,感谢您的回答。这对我帮助很大。我刚刚在下面发布了更完整的代码。如果你喜欢它,你可以将它添加到你自己的答案中,然后我会删除我的。
    【解决方案3】:

    这只是 mbroadst 的出色答案,稍作改动。这段代码应该是完整的,可以直接编译:

    database_manager.h:

    #ifndef DATABASEMANAGER_H
    #define DATABASEMANAGER_H
    
    #include <QMutex>
    #include <QHash>
    #include <QSqlDatabase>
    
    class QThread;
    
    class DatabaseManager
    {
        public:
            static QSqlDatabase database(const QString& connectionName = QLatin1String(QSqlDatabase::defaultConnection));
        private:
            static QMutex s_databaseMutex;
            static QHash<QThread*, QSqlDatabase> s_instances;
    };
    
    #endif // DATABASEMANAGER_H
    

    database_manager.cpp:

    #include "database_manager.h"
    
    #include <QSqlDatabase>
    #include <QMutexLocker>
    #include <QThread>
    #include <stdexcept>
    
    QMutex DatabaseManager::s_databaseMutex;
    QHash<QThread*, QHash<QString, QSqlDatabase>> DatabaseManager::s_instances;
    
    QSqlDatabase DatabaseManager::database(const QString& connectionName)
    {
        QMutexLocker locker(&s_databaseMutex);
        QThread *thread = QThread::currentThread();
    
        // if we have a connection for this thread, return it
        auto it_thread = s_instances.find(thread);
        if (it_thread != s_instances.end()) {
            auto it_conn = it_thread.value().find(connectionName);
            if (it_conn != it_thread.value().end()) {
                return it_conn.value();
            }
        }
    
        // otherwise, create a new connection for this thread
        QSqlDatabase connection = QSqlDatabase::cloneDatabase(
            QSqlDatabase::database(connectionName),
            QString("%1_%2").arg(connectionName).arg((int)thread));
    
        // open the database connection
        // initialize the database connection
        if (!connection.open()) {
            throw std::runtime_error("Unable to open the new database connection.");
        }
    
        s_instances[thread][connectionName] = connection;
        return connection;
    }
    

    用法:代替QSqlDatabase::database(),使用DatabaseManager::database()

    2016-07-01 编辑:修复了多个连接名称的错误

    【讨论】:

      猜你喜欢
      • 2011-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-24
      • 2011-11-12
      • 1970-01-01
      • 1970-01-01
      • 2017-03-16
      相关资源
      最近更新 更多