Qt多线程访问同一个数据库源码分享(基于Sqlite实现)
一、实现难点
多线程环境下多个线程同时访问同一个数据库会面临以下主要难点:
线程安全问题
数据库连接对象通常不是线程安全的,多个线程同时使用同一个连接会导致数据混乱或崩溃。每个线程需要独立的数据库连接。
死锁风险
多个线程同时执行事务操作时,如果锁定顺序不一致可能导致死锁。需要统一锁定顺序或使用超时机制。
连接管理问题
频繁创建和销毁连接会导致性能问题。可以使用连接池管理数据库连接。
数据一致性
多线程并发写入可能导致数据不一致。需要合理使用事务隔离级别和锁机制。
性能瓶颈
过多线程同时访问可能导致数据库成为性能瓶颈。需要限制最大并发线程数。
跨线程信号槽
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();
}