Qt多线程访问数据库注意事项以及解决办法

原文链接:https://blog.csdn.net/goldenhawking/article/details/10811409
第一篇:Qt访问多线程需要注意的问题整体性描述

彻底抛弃MFC, 全面应用Qt 已经不少时间了。除了自己看书按步就班做了十几个验证性的应用,还正式做了3个比较大的行业应用,总体感觉很好。Native C++ 下, Qt 基本是我用过的最简便的界面库了。遇到了一些问题,大都解决的很顺利,回头想想,还是有几个问题很有意思,尤其是数据库应用。这里把我的经历分享一下。

1、线程内注册与连接数据库的竞争问题

    文档上对多线程下数据库应用的注意事项写的很简明,一个线程创建的 QSqlDatabase 对象和 查出来的 QSqlQuery 对象只能给本线程用(注意,是对象,不是数据库连接本身,连接本身用名字可以多线程使用),其他情况是“不支持的”。在一个需要有几个线程并发访问不同数据库的应用中,我首先试图在各个线程的起始分别以不同的名称调用  addDatabase / database 、open,但是程序偶然会崩溃,跟踪后发现,虽然Qt 声称很多方法是“线程安全”的,但是几个方法串起来,就出问题了。Qt 会动态的加载数据库的plugin, 加载 plug in 的部分,涉及到对本地库文件的管理,这一部分,出现了竞争。于是,很自然的想到在初始连接部分设置 Mutex 保护,从 addDatabase / database到 open 的部分,要保证其原子性,问题再也没有出现。

2、数据库连接意外断裂后,恢复连接的问题

  在MFC 中,一旦中途TCP连接断裂,直接重新 Open 就可以了。在Qt 里,这一招不好使了。即便 调用了 close ,再次open 也是不行的。处理方法:

  在检测到问题出现后,关闭连接,并 removeDatabase;   而后,不要立刻 addDatabase, 反而是要回到该连接所在的事件循环。没有详细跟源码,很可能在 removeDatabase 后的事件循环中,Qt 内部做了一些释放操作。   怎么办呢, 可以设置一个恢复定时器,比如 1分钟,重新 addDatabase,就可以啦。如果心急的话,直接显式调用processEvent() 方法强制循环。

  在多线程下,注意1中的问题,需要 Mutex保护。

3、数据库插件的依赖性问题

  在 Windows 下,有时我们的机器上按了好几个 Qt 版本,PATH里索性神马也不设置,依赖开发环境的继承环境适应不同的版本。这有两个问题。一是发布程序的时候,数据库驱动依赖的dll 也要与可执行文件在同一路径下发布。比如 mysql 的 dll, PostgreSQL 的依赖等。二是在集成开发环境中,这些依赖也要位于执行档文件夹下。否则,会造成虽然可以枚举到可用驱动,但是死活连接不上。调试一下就知道,原来是在路径中找不到依赖项,导致dll加载失败哦!



 Qt的数据库操作自成一派,相对于复杂的 ADO \ODBC\DAO\OLEDB 等传统 C++ 访问数据库的方法,还是很先进的,充分体现了 OO 的理念。对数据库的封装,想法是很有意思的。设计者把进程内的数据库连接作为一种资源,每个连接有一个唯一的名字,可以通过全局的 addDatabase, removeDatabase, cloneDatabase 来增删,想用的时候,直接用全局的 database 来获取。这样的好处,是大大节省了开发者的负担。以前为了传递一个数据库连接的变量,必须在很多方法入库处添加指向这个变量的指针或者引用,有时候不得不在对象的属性中加入静态的变量,来记录这个连接。现在,什么时候想用,给个名字就可以了,不需要传递。当然,文档说的是比较简化的,至少有两点要注意,

 1)这些增删方法号称是线程安全的,但是,在实际应用中,还是要注意用 Mutex 保护全局创建流程,或者,重载这些函数,创建自己的安全版本。

 2)一个线程创建的数据库对象(如 addDatabase 的返回值)只能在同一线程使用,但是,addDatabase 注册的连接(名字是开发者定)可以跨线程使用,唯一需要注意的是,在调用全局方法的时候,要有原子保护。

————————————————
版权声明:本文为CSDN博主「丁劲犇」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

第二篇:实际应用中的细节以及代码举例
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sunbo94/article/details/79463680
最近做一个多线程的远程升级软件,做完后用一个对应的测试程序进行测试,发现线程一多必崩溃,而把所有数据库处理全部删掉后,就可以同时运行几百个线程不崩溃了.原因应该是自己采用了单例模式,在各个线程中发送信号给TcpServer,在server中统一单线程调用SqliteTool的一个单例操作数据库,原以为这样应该是更安全和高效的,现在看应该用多线程加静态函数的方式了.

1.单例模式不要用了,多线程访问一个对象就是坑.Java下的连接池管理对象是单例,但是连接池对象是多个的.所以静态函数就可以了

#include “dbtool.h”
#include
#include
#include
#include
#include

QMutex DBTool::mutex4Thread;
DBTool::DBTool()
{
qDebug()<<“DBTool()”;
}

DBTool::~DBTool()
{
qDebug()<<“DBTool()”;
}

QSqlDatabase DBTool::getConnection()
{
QSqlDatabase db;
QString dbType=“QSQLITE”;
QString connectionName=QString::number((quint64)(QThread::currentThread()),16);
QString dbName=QCoreApplication::applicationDirPath()+"/db/sqlite3.db";
if(QSqlDatabase::contains(connectionName))
{
qDebug()<<“ERROR:db=QSqlDatabase::database(connectionName);”;
db=QSqlDatabase::database(connectionName);
}
else
{
db=QSqlDatabase::addDatabase(dbType,connectionName);
}
db.setDatabaseName(dbName);
if(!db.open())
{
qDebug()<<“open error”<<db.lastError().text();
}
return db;
}

void DBTool::removeConnection()
{
QString connectionName=QString::number((quint64)(QThread::currentThread()),16);
if(QSqlDatabase::contains(connectionName))
{
QSqlDatabase::removeDatabase(connectionName);
}
}
使用的时候一要加锁,必须加,二QSqlDatabase对象搞个局部的,然后remove

void TcpThread::db_progress(double progress)
{
QMutexLocker locker(&DBTool::mutex4Thread);
QString progressStr=QString::number(progress*100,‘f’,2)+"%";
{
QSqlDatabase db=DBTool::getConnection();
QSqlQuery query(db);
query.prepare(“UPDATE table_all set progress = :progress WHERE id LIKE :id”);
query.bindValue(":progress",progressStr);
query.bindValue(":id",_id);
if(!query.exec())
{
qDebug()<<“query exec error5:”<<query.lastError().text();
}
}
DBTool::removeConnection();
}

2.更新数据库界面,Query操作放到子线程里区,不要在主线程做耗时操作

#include “sql4ui.h”
#include
#include
#include “tool/dbtool.h”
#include
#include
#include
#include “global.h”
#include

Sql4ui::Sql4ui(QObject *obj, int request, int page_num, QString filter_sql, QString query_filter) : QRunnable()
{
_obj=obj;
_request=request;
_page_num=page_num;
_filter_sql=filter_sql;
_query_filter=query_filter;
}

Sql4ui::~Sql4ui()
{
}

void Sql4ui::run()
{
if(_request0) //更新界面
{
refresh_table();
}
else if(_request
1) //根据条件查询数目
{
query_count();
}
}

void Sql4ui::refresh_table()
{
{
QSqlDatabase db=DBTool::getConnection();
int count=0;
//QString cells[ROW_OF_TABLE][COLUMN_OF_TABLE];
//使用[]要先resize
QVector<QVector> cells(ROW_OF_TABLE);
for(int i=0;i<ROW_OF_TABLE;i++)
{
QVector vec(COLUMN_OF_TABLE);
cells[i]=vec;
}
{
QMutexLocker locker(&DBTool::mutex4Thread);
//查询符合where_sql的记录总数count
QSqlQuery query1(“SELECT count(*) FROM table_all”+_filter_sql,db);
if(query1.next())
{
count=query1.value(0).toInt();
}
QSqlQuery query(db);
int start=(_page_num-1)ROW_OF_TABLE;
if(_filter_sql!="")
{
query.prepare(“SELECT * FROM table_all “+_filter_sql+” ORDER BY id”);
if(query.exec())
{
int rowIndex=0;
if(query.seek(start))
{
bool is_next_ok=true;
while(is_next_ok&&rowIndex<ROW_OF_TABLE)
{
cells[rowIndex][0]=query.value(“id”).toString();
cells[rowIndex][1]=query.value(“SA”).toString();
cells[rowIndex][2]=query.value(“status”).toString();
cells[rowIndex][3]=query.value(“progress”).toString();
cells[rowIndex][4]=query.value(“error”).toString();
cells[rowIndex][5]=query.value(“datetime”).toString();
rowIndex++;
is_next_ok=query.next();
}
}
}
}
else //无筛选大数据时这样更快
{
int end=_page_num
ROW_OF_TABLE;
query.prepare(“SELECT * FROM table_all WHERE id > :start AND id <= :end”);
query.bindValue(":start",start);
query.bindValue(":end",end);
if(query.exec())
{
int rowIndex=0;
while(query.next())
{
cells[rowIndex][0]=query.value(“id”).toString();
cells[rowIndex][1]=query.value(“SA”).toString();
cells[rowIndex][2]=query.value(“status”).toString();
cells[rowIndex][3]=query.value(“progress”).toString();
cells[rowIndex][4]=query.value(“error”).toString();
cells[rowIndex][5]=query.value(“datetime”).toString();
rowIndex++;
}
}
}
}
QMetaObject::invokeMethod(_obj,“invokeTable”,Qt::QueuedConnection,Q_ARG(int,count),Q_ARG(QVector<QVector>,cells));
}
DBTool::removeConnection();
}

void Sql4ui::query_count()
{
{
QSqlDatabase db=DBTool::getConnection();
int count=0;
{
QMutexLocker locker(&DBTool::mutex4Thread);
QSqlQuery query(db);
query.prepare("SELECT count(*) FROM table_all "+_query_filter);
if(query.exec())
{
if(query.next())
{
count=query.value(0).toInt();
}
}
}
QMetaObject::invokeMethod(_obj,“invokeQueryCount”,Qt::QueuedConnection,Q_ARG(int,count));
}
DBTool::removeConnection();
}

引用

Qt数据库由QSqlDatabase::addDatabase()生成的QSqlDatabase只能在创建它的线程中使用, 在多线程中共用连接或者在另外一个线程中创建query都是不支持的

假设有如下代码:
bool openDatabase()
{
QSqlDatabase db;
QString connectionName = “sqlite”;
db = QSqlDatabase::addDatabase(“QSQLITE”, connectionName);
db.setDatabaseName("/jyxtec.db");
if (db.open()) return true;
else return false;
}
void testQuery()
{
QSqlQuery query(QSqlDatabase::database(“sqlite”));
query.exec(“SELECT * from t_test”);
}
这里的testQuery()是不支持多线程调用的,只能在调用OpenDatabase()的线程中使用.否则很容易段错误。
解决方法有两种:
1)每个调用testQuery的线程中创建不同connectionName的QSqlDatabase
比如线程A
QSqlDatabase::addDatabase(“QSQLITE”, “A”);
QSqlQuery query(QSqlDatabase::database(“A”));
线程B
QSqlDatabase::addDatabase(“QSQLITE”, “B”);
QSqlQuery query(QSqlDatabase::database(“B”));

2)实现一个数据库线程池,创建N个不同connectionName的QSqlDatabase,所有的query命令都放到这个线程池中处理。
————————————————
版权声明:本文为CSDN博主「sunbo94」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sunbo94/article/details/79463680

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值