Qt多线程访问同一个数据库源码分享(基于Sqlite实现)

Qt多线程访问同一个数据库源码分享(基于Sqlite实现)

一、实现难点

多线程环境下多个线程同时访问同一个数据库会面临以下主要难点:

线程安全问题

数据库连接对象通常不是线程安全的,多个线程同时使用同一个连接会导致数据混乱或崩溃。每个线程需要独立的数据库连接。

死锁风险

多个线程同时执行事务操作时,如果锁定顺序不一致可能导致死锁。需要统一锁定顺序或使用超时机制。

连接管理问题

频繁创建和销毁连接会导致性能问题。可以使用连接池管理数据库连接。

数据一致性

多线程并发写入可能导致数据不一致。需要合理使用事务隔离级别和锁机制。

性能瓶颈

过多线程同时访问可能导致数据库成为性能瓶颈。需要限制最大并发线程数。

跨线程信号槽

Qt要求数据库对象必须在创建它的线程中使用。跨线程操作需要特别注意。

最佳实践建议

使用Qt的线程模块时,遵循以下原则可减少问题:

  • 每个线程使用独立的数据库连接
  • 合理使用事务和锁机制
  • 考虑使用连接池管理连接
  • 控制最大并发线程数
  • 避免跨线程传递数据库对象

商业数据库通常提供更好的多线程支持,SQLite等嵌入式数据库在多线程环境下需要特别注意。### 多线程数据库访问的难点

二、源码分享

由于多个线程访问同一个数据库所以用一个单例类来管理数据库,实现如下:

sqliteHelper.h

cpp 复制代码
#ifndef SQLITEHELPER_H
#define SQLITEHELPER_H

#include <QObject>
#include <QtSql>
#include <QString>
#include <QMutex>
#include <QMutexLocker>
#include <QWaitCondition>
#include <QQueue>
#include <QThread>

class SqliteHelper
{
private:
    SqliteHelper();
    SqliteHelper(SqliteHelper& ) = delete;
    SqliteHelper operator=(const SqliteHelper &) = delete;
public:
    ~SqliteHelper();
    static SqliteHelper *getInstance();

    static void changeDatabase(QString databaseName);
    bool lockExec(QString sql);
    QSqlDatabase *getDatabase();
    static void quit();
private:
    static void removeDatabases();
private:
    static QMutex mutexCreateSql,mutexUpdateSql;
    QString strConnName;
    static QString currentDatabaseName;
    static QHash<Qt::HANDLE, SqliteHelper*> databaseMap;//所有数据库链接,key: 线程ID,
};

#endif // SQLITEHELPER_H

sqliteHelper.cpp

cpp 复制代码
#include "sqliteHelper.h"

QMutex SqliteHelper::mutexCreateSql;
QMutex SqliteHelper::mutexUpdateSql;
QHash<Qt::HANDLE, SqliteHelper*> SqliteHelper::databaseMap;
QString SqliteHelper::currentDatabaseName;

SqliteHelper::SqliteHelper()
{

    mutexCreateSql.lock();

    Qt::HANDLE id = QThread::currentThreadId();
    strConnName = QString::number(*(unsigned int*)&id);
    QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE", strConnName);
    database.setDatabaseName(currentDatabaseName);
    qDebug()<<"SQLiteHelper()  "<<strConnName;
    mutexCreateSql.unlock();
}

SqliteHelper::~SqliteHelper()
{

}

SqliteHelper *SqliteHelper::getInstance()
{
    if(!databaseMap.contains(QThread::currentThreadId())) {
        databaseMap.insert(QThread::currentThreadId(), new SqliteHelper());
    }

    return databaseMap[QThread::currentThreadId()];
}

void SqliteHelper::changeDatabase(QString databaseName)
{
    if(databaseName.isEmpty())
        return;
    SqliteHelper::removeDatabases();
    currentDatabaseName = databaseName;
    qDebug()<<databaseName;
}



bool SqliteHelper::lockExec(QString sql)
{
    mutexUpdateSql.lock();
    QSqlDatabase sqlDb =QSqlDatabase::database(strConnName);
    if(!sqlDb.isOpen())
    {
        mutexCreateSql.lock();
        sqlDb.open();
        mutexCreateSql.unlock();
    }
    QSqlQuery sqlQuery(sqlDb);
    sqlQuery.prepare(sql);

    bool res = sqlQuery.exec();
    if(!res)
        qDebug()<<sqlQuery.lastError().text();

    sqlDb.close();
    mutexUpdateSql.unlock();
    return res;
}
QSqlDatabase *SqliteHelper::getDatabase()
{
    QSqlDatabase *sqlDb = new QSqlDatabase(QSqlDatabase::database(strConnName));
    if(!sqlDb->isOpen())
    {
        mutexCreateSql.lock();
        sqlDb->open();
        mutexCreateSql.unlock();
    }
    return sqlDb;
}

void SqliteHelper::quit()
{
    currentDatabaseName = "";
    removeDatabases();
}

void SqliteHelper::removeDatabases()
{
    qDebug()<<"SQLiteHelper::removeDatabases()";
    QList<Qt::HANDLE> keys = databaseMap.keys();
    for(int i= 0; i<keys.count();i++)
    {
        Qt::HANDLE id = keys[i];

        //释放内存
        delete databaseMap.take(id);
        QSqlDatabase::removeDatabase(QString::number(*(unsigned int*)&id));
    }
}

三、测试

1、新建一个多线程类

thread.h

cpp 复制代码
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QObject>
#include <QString>

#include "sqlitehelper.h"

class MyThread:public QThread
{
public:
    MyThread();


public:
    void run() override;
};

#endif // MYTHREAD_H

thread.cpp

cpp 复制代码
#include "mythread.h"

MyThread::MyThread()
{

}

void MyThread::run()
{
    static int cnt = 0;
    while(1)
    {
        QThread::sleep(1);

        auto id = QThread::currentThreadId();

        int barCode = 0, waybillCode = 0;
        barCode = *(unsigned int*)&id + cnt;
        waybillCode = *(unsigned int*)&id + cnt;

        cnt++;

        QString sql = QString(R"(INSERT INTO produceTable(barCode,waybillCode,dateTime) VALUES('%1','%2',datetime(CURRENT_TIMESTAMP, 'localtime'));)")
                          .arg("barCode"+QString::number(barCode)).arg("waybillCode"+QString::number(waybillCode));

        SqliteHelper* sqlHelper = SqliteHelper::getInstance();
        qDebug()<<id<<"  "<<sqlHelper->lockExec(sql);


    }


}

2、开启多线程插入数据

连接一个数据库:

cpp 复制代码
SqliteHelper::changeDatabase("database2.db");

界面中放置一个按钮,按几下开启几个线程。

cpp 复制代码
void MainWindow::on_btnInsertData_clicked()
{

    MyThread *t = new MyThread();
    t->start();

}
相关推荐
迎風吹頭髮1 小时前
UNIX下C语言编程与实践9-UNIX 动态库创建实战:gcc 参数 -fpic、-shared 的作用与动态库生成步骤
c语言·数据库·unix
黑马金牌编程2 小时前
深入浅出 Redis:从核心原理到运维实战指南一
数据库·redis·缓存·性能优化·非关系型数据库
李迟2 小时前
2025年9月个人工作生活总结
服务器·数据库·生活
海涛高软4 小时前
qt使用opencv的imread读取图像为空
qt·opencv·webpack
野犬寒鸦4 小时前
从零起步学习Redis || 第四章:Cache Aside Pattern(旁路缓存模式)以及优化策略
java·数据库·redis·后端·spring·缓存
茉莉玫瑰花茶5 小时前
Redis - Bitfield 类型
数据库·redis·缓存
lang201509285 小时前
MySQL InnoDB备份恢复全指南
数据库·mysql
爱吃香蕉的阿豪6 小时前
.NET Core 中 System.Text.Json 与 Newtonsoft.Json 深度对比:用法、性能与场景选型
数据库·json·.netcore
mpHH6 小时前
postgresql中的默认列
数据库·postgresql
jllws16 小时前
数据库原理及应用_数据库基础_第4章关系模型的基本理论_数据库完整性规则和MySQL提供的约束
数据库