Qt多線程訪問同一個數據庫源碼分享(基于Sqlite實現)
- 一、實現難點
- 線程安全問題
- 死鎖風險
- 連接管理問題
- 數據一致性
- 性能瓶頸
- 跨線程信號槽
- 最佳實踐建議
- 二、源碼分享
- 三、測試
- 1、新建一個多線程類
- 2、開啟多線程插入數據
一、實現難點
多線程環境下多個線程同時訪問同一個數據庫會面臨以下主要難點:
線程安全問題
數據庫連接對象通常不是線程安全的,多個線程同時使用同一個連接會導致數據混亂或崩潰。每個線程需要獨立的數據庫連接。
死鎖風險
多個線程同時執行事務操作時,如果鎖定順序不一致可能導致死鎖。需要統一鎖定順序或使用超時機制。
連接管理問題
頻繁創建和銷毀連接會導致性能問題。可以使用連接池管理數據庫連接。
數據一致性
多線程并發寫入可能導致數據不一致。需要合理使用事務隔離級別和鎖機制。
性能瓶頸
過多線程同時訪問可能導致數據庫成為性能瓶頸。需要限制最大并發線程數。
跨線程信號槽
Qt要求數據庫對象必須在創建它的線程中使用。跨線程操作需要特別注意。
最佳實踐建議
使用Qt的線程模塊時,遵循以下原則可減少問題:
- 每個線程使用獨立的數據庫連接
- 合理使用事務和鎖機制
- 考慮使用連接池管理連接
- 控制最大并發線程數
- 避免跨線程傳遞數據庫對象
商業數據庫通常提供更好的多線程支持,SQLite等嵌入式數據庫在多線程環境下需要特別注意。### 多線程數據庫訪問的難點
二、源碼分享
由于多個線程訪問同一個數據庫所以用一個單例類來管理數據庫,實現如下:
sqliteHelper.h
#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
#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
#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
#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、開啟多線程插入數據
連接一個數據庫:
SqliteHelper::changeDatabase("database2.db");
界面中放置一個按鈕,按幾下開啟幾個線程。
void MainWindow::on_btnInsertData_clicked()
{MyThread *t = new MyThread();t->start();}