QT实现检测MySQL数据库连接状态并断开重连

背景

在QT中做有关于MySQL的业务时,有时候需要知道MySQL数据库的当前连接状态。但QT在提供的QSqlDatabase模块中并没有相关的功能,不像QTcpSocket在连接服务或断开服务时都有相应的信号发出。

网上方法

  • (1)使用QSqlDatabase::isOpen()判断

经测试,在突然与数据库所在网络断开后,该接口仍返回true,不靠谱。

  • (2)设置连接参数setConnectOptions(“MYSQL_OPT_RECONNECT=1”)

未测试,看网上的说法是在长时间未操作(默认8小时)数据库则会与数据库断开,如设置这个参数则会重连,但不知道在断网后的情况怎样。

  • (3)定时执行sql语句来判断

使用定时器,定时执行sql语句(“select 1”)来判断数据库是否连接正常。经测试,在突然断网的情况下,执行sql语句会阻塞20秒甚至更长的时间(不知道这个时间是由什么决定的?可否设置?);但若是数据库服务挂掉,则不会阻塞。

解决方法

采用第3种方法 + 每次执行sql前ping一下网络是否连接正常,以此来避免断网时执行sql的阻塞。
(1)子线程中创建专门的连接来检测数据库连接状态;
(2)其他线程则创建自己的连接来执行相应的业务;并可根据子线程检测的状态结果来做相应的判断。
如果有别的方法,还请大佬们指导一下

代码

.h文件

#ifndef DBHEARTBEATTHREAD_H
#define DBHEARTBEATTHREAD_H

#include 
#include 
#include 

class DBHeartbeatThread : public QThread
{
    Q_OBJECT

public:
    enum DBType : quint8 {
        MySQL = 0,
        SqlServer,
        Oracle
    };

    struct ConnectParam {
        QString ip;
        quint16 port;
        QString dbName;
        QString userName;
        QString password;
    };

public:
    explicit DBHeartbeatThread(QObject *parent = nullptr);
    explicit DBHeartbeatThread(const DBType &type, const ConnectParam &param, QObject *parent = nullptr);

public:
    void setDBTypeAndConnectParm(const DBType &type, const ConnectParam &param);
    void setCheckIpIsOnlineInterval(int msec);
    void setConnectDBInterval(int msec);
    void setCheckDBHeartbeatInterval(int msec);
    QString lastError() const;

protected:
    void run() override;

signals:
    void signal_connectedDB();       // 数据库连接成功
    void signal_disconnectedDB();    // 与数据库断开连接
    void signal_connectedNet();      // 网络连接成功
    void signal_disconnectedNet();   // 网络断开连接

private:
    DBType m_type;
    ConnectParam m_param;
    int m_checkIpIsOnlineInterval;
    int m_connectDBInterval;
    int m_checkDBHeartbeatInterval;

    QMap<DBType, QString> m_mapDBTypeDriver;
    QString m_lastError;

    const int kCheckIpFailTime = 3;    // 检测网络心跳失败最大次数
    const int kCheckDBFailTime = 3;    // 检测数据库心跳失败最大次数
};

#endif // DBHEARTBEATTHREAD_H

.cpp文件

#include "dbheartbeatthread.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 

DBHeartbeatThread::DBHeartbeatThread(QObject *parent) : QThread(parent)
{
    m_checkIpIsOnlineInterval = 100;
    m_connectDBInterval = 100;
    m_checkDBHeartbeatInterval = 100;
    m_lastError = "";

    m_mapDBTypeDriver = {{MySQL, "QMYSQL"}, {SqlServer, "QODBC"}, {Oracle, "QOCI"}};
}

DBHeartbeatThread::DBHeartbeatThread(const DBType &type, const ConnectParam &param, QObject *parent)
    : DBHeartbeatThread(parent)
{
    m_type = type;
    m_param = param;
}

void DBHeartbeatThread::setDBTypeAndConnectParm(const DBHeartbeatThread::DBType &type,
        const DBHeartbeatThread::ConnectParam &param)
{
    m_type = type;
    m_param = param;
}

void DBHeartbeatThread::setCheckIpIsOnlineInterval(int msec)
{
    m_checkIpIsOnlineInterval = msec;
}

void DBHeartbeatThread::setConnectDBInterval(int msec)
{
    m_connectDBInterval = msec;
}

void DBHeartbeatThread::setCheckDBHeartbeatInterval(int msec)
{
    m_checkDBHeartbeatInterval = msec;
}

QString DBHeartbeatThread::lastError() const
{
    return m_lastError;
}

void DBHeartbeatThread::run()
{
    /// 数据库连接异常:
    /// 1、断网,用checkIpIsOnline判断
    /// 2、数据库服务挂了,在MySQL上测试query.exec(sql)不会阻塞,会直接就是执行失败
    QSqlDatabase db;

    // 打开数据库
    auto openDB = [&]() -> bool {
        static QString connectName = "heartbeatCheck";
        if (QSqlDatabase::contains(connectName)) {
            db = QSqlDatabase::database(connectName);
        } else {
            db = QSqlDatabase::addDatabase(m_mapDBTypeDriver.value(m_type), connectName);
        }

        if (db.isOpen()) {
            db.close();
        }

        db.setHostName(m_param.ip);
        db.setPort(m_param.port);
        db.setDatabaseName(m_param.dbName);
        db.setUserName(m_param.userName);
        db.setPassword(m_param.password);

        QTime startTime = QTime::currentTime();
        if (db.open()) {
            QTime endTime = QTime::currentTime();
            qDebug() << "数据库打开成功耗时: " << startTime.msecsTo(endTime) << "ms";
            return true;
        }

        QTime endTime = QTime::currentTime();
        qDebug() << "数据库打开失败耗时: " << startTime.msecsTo(endTime) << "ms";
        m_lastError = QString("数据库打开失败,原因:%1").arg(db.lastError().text());
        return false;
    };

    // 检测数据库是否在线(执行sql操作)
    auto checkDBIsOnline = [&db]() -> bool {
        QSqlQuery query(db);
        QString sql = "select 1";

        QTime startTime = QTime::currentTime();
        if (query.exec(sql)) {
            return true;
        }

        QTime endTime = QTime::currentTime();
        qDebug() << "检测心跳断开耗时:" << startTime.msecsTo(endTime) << "ms";
        return false;
    };

    // 检测IP是否在线
    auto checkIpIsOnline = [](const QString & ip) -> bool {
        QProcess cmd;
        QString command = "";
#ifdef Q_OS_WIN
        /// -n 要发送的回显请求数
        /// -w 等待每次回复的超时时间(毫秒)
        command = QString("ping %1 -n 1 -w 1000").arg(ip);
#else
        /// -s ping发送的字节数
        /// -c ping的次数
        command = QString("ping -s 1 -c 1 %1").arg(ip);
#endif
        cmd.start(command);
        cmd.waitForFinished(1000 * 1);

        QString retStr = cmd.readAll();
        if (retStr.indexOf("ttl", 0, Qt::CaseInsensitive) == -1) {
            return false;
        }

        return true;
    };

    QTimer timerCheckIp;       // 检测IP是否在线定时器
    QTimer timerConnectDB;     // 连接数据库定时器
    QTimer timerDBHeartbeat;   // 检测数据库心跳定时器

    timerCheckIp.setInterval(m_checkIpIsOnlineInterval);
    connect(&timerCheckIp, &QTimer::timeout, [&] {
        timerCheckIp.stop();
        if (checkIpIsOnline(m_param.ip)) {
            qDebug() << "连接网络成功,正在连接数据库";
            emit signal_connectedNet();
            timerConnectDB.start();
        } else {
            timerCheckIp.start();
        }
    });

    timerConnectDB.setInterval(m_connectDBInterval);
    connect(&timerConnectDB, &QTimer::timeout, [&] {
        timerConnectDB.stop();
        if (checkIpIsOnline(m_param.ip)) {
            if (openDB()) {
                qDebug() << "连接数据库成功,开始心跳检测";
                emit signal_connectedDB();
                timerDBHeartbeat.start();
            } else {
                timerConnectDB.start();
            }
        } else {
            qDebug() << "网络断开,正在重连";
            emit signal_disconnectedNet();
            timerCheckIp.start();
        }
    });

    timerDBHeartbeat.setInterval(m_checkDBHeartbeatInterval);
    connect(&timerDBHeartbeat, &QTimer::timeout, [&] {
        static int checkIpFailTime = 0;
        static int checkDBFailTime = 0;
        timerDBHeartbeat.stop();

        if (checkIpIsOnline(m_param.ip)) {
            checkIpFailTime = 0;

            if (checkDBIsOnline()) {
                checkDBFailTime = 0;
                timerDBHeartbeat.start();
            } else {
                ++checkDBFailTime;
                if (checkDBFailTime == kCheckDBFailTime) {
                    checkDBFailTime = 0;
                    qDebug() << "数据库断开,正在重连";
                    emit signal_disconnectedDB();
                    timerConnectDB.start();
                } else {
                    timerDBHeartbeat.start();
                }
            }
        } else {
            ++checkIpFailTime;
            if (checkIpFailTime == kCheckIpFailTime) {
                checkIpFailTime = 0;
                qDebug() << "网络断开,正在重连";
                emit signal_disconnectedNet();
                timerCheckIp.start();
            } else {
                timerDBHeartbeat.start();
            }
        }
    });

    timerCheckIp.start();
    qDebug() << "正在连接网络";
    exec();
}

你可能感兴趣的:(QT,·,随记,qt,mysql,断开重连)