有一种 Qt 数据库问题特别烦。
程序刚启动一切正常,查询快、界面顺,运行几个小时后却开始越来越慢。
重启一下,又恢复正常。
这时候很多人第一反应是查 SQL、查索引、查数据库性能。
但我实际项目里遇到过不少情况,问题根本不在 SQL,而在:
QSqlQuery
Demo 没问题,项目为什么会出事
普通代码通常这样写:
QSqlQuery query(db);
query.exec("SELECT id, name FROM device");
while (query.next()) {
// 处理数据
}
这种写法本身没问题。
真正容易踩坑的是把 QSqlQuery 做成长生命周期对象:
class DeviceManager
{
private:
QSqlQuery m_query;
};
很多人觉得这样可以复用对象。
但 QSqlQuery 背后可能还关联着查询结果、游标和数据库驱动资源。
也就是说:
业务代码已经不用了,不代表底层查询资源已经结束。
Demo 查询次数少、运行时间短,所以很难暴露。
真实项目一跑就是几天,再叠加定时查询、日志查询、设备状态刷新,问题就会慢慢出来。
常见表现就是:
- 内存缓慢上涨
- 查询越来越慢
- 数据库连接长期被占用
- 页面刷新开始卡顿
最麻烦的是,它通常不会直接报错。
我现在更推荐局部 QSqlQuery
普通查询我基本都会这样写:
bool DeviceManager::loadStatus(int id)
{
QSqlQuery query(m_db);
query.prepare(
"SELECT status FROM device WHERE id = ?"
);
query.addBindValue(id);
if (!query.exec()) {
qWarning() << query.lastError();
return false;
}
if (query.next()) {
const int status = query.value(0).toInt();
handleStatus(status);
}
return true;
}
重点不是 SQL。
而是:
QSqlQuery query(m_db);
它是局部变量。
函数结束,查询对象生命周期自然结束,资源边界也非常清楚。
相比“省一次对象创建”,我更在意数据库资源什么时候释放。
必须复用时,记得结束结果集
有些场景确实需要复用查询对象,比如高频轮询。
这时候处理完结果以后,可以主动:
if (query.exec()) {
while (query.next()) {
processData(query.value(0));
}
query.finish();
}
finish() 的意义很简单:
这次查询结果已经不用了,可以结束当前结果集。
在定时查询、工业监控、日志系统、后台数据库线程里,这个习惯很重要。
别机械地到处 clear()
有人发现资源问题后,会开始疯狂写:
query.finish();
query.clear();
其实没必要。
我的经验是:
短生命周期查询,让作用域自动管理。
长生命周期、需要复用的查询对象,一轮处理结束后及时 finish()。
真正要解决的,不是“多调用几个释放函数”,而是:
QSqlQuery 为什么要活这么久?
大结果集更容易放大问题
还有一种典型写法:
SELECT * FROM log_table
一次拉几万条数据,再配合长生命周期 QSqlQuery,问题会更明显。
实际项目更建议控制范围:
query.prepare(
"SELECT time, level, content "
"FROM log_table "
"ORDER BY time DESC "
"LIMIT 1000"
);
数据库能查出来,不代表客户端应该一次全拿回来。
我的经验总结
如果 Qt 程序出现:
刚启动快,运行几个小时后越来越慢。
除了查 SQL 和索引,我一定会顺手检查:
QSqlQuery 是不是成员变量、查询结果是不是长期保留、有没有高频定时查询、一次返回的数据是不是太多。
尤其是工业软件、MES、SCADA、设备监控、日志系统这类需要 7×24 小时运行的项目,更要注意。
我现在基本遵循一个原则:
QSqlQuery 能局部就局部,结果用完及时结束,一次查询别拿太多数据。
Demo 能跑,只代表代码能执行。
项目能不能稳定跑一个月,看的往往就是这些不起眼的生命周期细节。