资讯动态

Qt 数据库连接别偷懒全局复用,多线程里真的会出事

发布时间:2026/8/7 4:25:54 来源:尧图企业网站定制
这个坑我第一次踩是在工业上位机项目里。前期开发特别顺。数据库模块封装一个DbManager里面放个全局QSqlDatabase谁要查数据直接调。开发机单线程测试几个月都没问题。结果现场一上多线程采集问题开始离谱。有时候报QSqlDatabasePrivate::addDatabase:duplicate connection name有时候QSqlQuery::exec:databasenotopen更恶心的是偶发。一天能复现一次你根本抓不到规律。后来才真正意识到Qt 的数据库连接本质上不是线程安全资源。很多人第一次用QSqlDatabase都会下意识把它当成“全局单例连接池”。但 Qt 官方其实早就暗示了数据库连接属于创建它的线程。问题是 demo 太容易骗人了。单线程里全局连接当然没事。UI 线程查数据库、写日志、读配置全都正常。但项目一旦开始TCP 收包线程串口采集线程数据库存储线程日志线程后台统计线程这些线程同时访问一个连接迟早开始炸。尤其 MySQL 和 SQLite更容易把问题放大。为什么 demo 没问题项目里就开始炸Qt 的数据库模块内部其实维护了一套连接表。你写QSqlDatabase dbQSqlDatabase::addDatabase(QMYSQL,main_conn);这个main_conn会被注册到全局连接池。问题来了。很多人在线程里继续这么写QSqlDatabase dbQSqlDatabase::database(main_conn);看起来只是“拿已有连接”。实际上你已经跨线程用了别人的连接。Qt 不会立刻崩。它会在高并发、频繁查询、线程退出时开始随机出异常。这种 bug 最烦的地方就在于它不像空指针那样稳定崩它是随机坏。现场机器压力一上来你就开始背锅。项目里我一般会这么处理我现在基本遵循一个原则一个线程一个数据库连接。不要共享连接。不要全局复用。更不要图省事把QSqlDatabase做成单例。项目里我一般这么写QString connNameQString(db_%1).arg((quintptr)QThread::currentThreadId());QSqlDatabase dbQSqlDatabase::addDatabase(QMYSQL,connName);线程启动时创建。线程结束时销毁。每个线程拿自己的连接。这样虽然连接数量多一点但稳定性会好非常多。尤其是日志存储、历史数据落库这种后台线程独立连接真的很重要。因为数据库驱动内部很多状态根本不是给跨线程共享设计的。真正麻烦的其实是连接释放很多项目只管创建不管释放。这也是后面越来越乱的根源。Qt 数据库连接不是对象析构就完事。你必须db.close();QSqlDatabase::removeDatabase(connName);而且这里还有个坑。removeDatabase 前相关 QSqlQuery 必须先析构。否则你会看到经典报错QSqlDatabasePrivate::removeDatabase:connection is still in use这个问题在长时间运行项目里特别明显。现场程序跑几天后连接数越来越高MySQL 被打满SQLite 文件锁死查询越来越慢最后大家开始怀疑数据库性能。其实根源是连接泄漏。常见坑或经验提醒很多人以为QSqlDatabase db;离开作用域就安全了。其实不是。QSqlDatabase本身只是个轻量句柄真正连接还在 Qt 内部。另外还有个误区“我加个 mutex 不就线程安全了”不好意思很多时候没用。因为问题不只是竞争访问。而是数据库连接内部状态、驱动上下文、线程归属本来就不该跨线程。锁只能减少概率不能解决设计问题。还有一点我现在很坚持数据库操作不要混进 UI 线程。Qt UI 卡不卡很多时候不是绘制问题而是你偷偷在主线程里查数据库。尤其分页查询、大批量插入、历史记录加载。界面一转圈用户第一反应就是“软件卡死了。”最后说两句Qt 数据库模块最大的问题就是太“好用了”。好用到很多人忘了它背后其实还是数据库连接。而数据库连接从来都不是随便共享的资源。我现在越来越觉得Qt 真正难的从来不是 API 会不会用而是生命周期和线程边界。数据库连接、多线程对象、信号槽、网络 socket本质都一样。demo 能跑不代表项目设计是对的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价