资讯动态

Qt数据库开发实战:从SQLite到MySQL的驱动配置与数据操作

发布时间:2026/10/17 19:42:28 来源:尧图企业网站定制
1. 为什么Qt要自己做一套数据库接口驱动清单与选型逻辑先把话说在前面我这份笔记已经写到第三十三篇了前面三十多篇几乎都在折腾界面、信号槽、事件循环这些东西数据库这块一直没碰。原因也很简单——桌面应用如果只是展示数据、接收用户输入用个配置文件、JSON、甚至二进制文件都能凑合。但一旦涉及大量结构化数据的查询、统计、多表关联再头铁也绕不开数据库。Qt在数据库这件事上的做法和其他C项目有一个非常明显的差异它没有推荐你直接去调某一个数据库的原生API而是自己封装了一套数据库模块也就是QtSql。这套模块的抽象目标是你写的数据库代码换一个底层数据库时尽量少改甚至不改业务逻辑。说得更直白一点你在代码里操作的是QSqlDatabase、QSqlQuery、QSqlTableModel这些Qt类真正的数据库驱动被藏在了后面。1.1 从SQLite到MySQLQt官方支持的驱动全景打开Qt文档你会看到QSqlDatabase::drivers()这个函数它能列出当前Qt版本下可用的数据库驱动。常见的包括QSQLITESQLite驱动内置在Qt中不需要额外安装数据库服务是对初学者最友好、也是我个人最推荐用来入门的一个。QMYSQL / QMYSQL3MySQL驱动旧版本还区分过3和8新版本默认都叫QMYSQL。QPSQLPostgreSQL驱动。QODBC通过ODBC桥接的方式连接各种数据库包括SQL Server、Access等。QOCIOracle的驱动。这些驱动有一个共同点不全是静态编译进Qt的。SQLite特殊一点它一般作为内置驱动直接可用其余像MySQL、PostgreSQL这些往往需要对应版本的驱动插件而且这个插件还和Qt的编译版本、编译器位数严格绑定。很多新同学在这里栽过跟头——代码写的没问题一运行就提示driver not loaded。1.2 表格式对比针对不同项目规模怎么选我是从实际项目角度出发做了个对比表你在选题之前可以对照着看数据库优点缺点适合场景SQLite零配置、单文件、跨平台、Qt内置驱动高并发写入弱、不适合网络服务桌面客户端、小工具、原型验证MySQL成熟稳定、并发好、生态丰富需要安装服务端、驱动配置有坑Web后端、客户端上报数据到中心库PostgreSQL功能强、支持复杂查询、JSON处理好学习成本略高、驱动需要单独准备数据分析、需要复杂事务和约束的业务ODBC可以桥接到几乎任何数据库配置依赖Windows系统机制、效率略低企业环境对接既有系统我的建议很简单学习阶段直接选SQLite把Qt的API逻辑吃透如果工作项目需要对接MySQL这类服务器型数据库再看第二部分怎么配置驱动和连接参数。1.3 理解Qt的连接到底是个什么概念在Qt里一个数据库连接是由连接名connection name唯一标识的。默认连接名是qt_sql_default_connection你调用QSqlDatabase::addDatabase()时如果只传驱动类型就会使用这个默认连接名。如果同一个连接名被连续添加两次Qt会警告并替换掉之前的连接。我的实践经验是连接是QSqlDatabase类的实例不是QSqlQuery的实例。你用一个连接执行了多个查询这是完全正常的但不要把打开的文件、数据库句柄混在一起理解。数据库连接本身也没必要频繁开关一个应用维护一个或少数几个长连接就够了。2. 从零建立连接SQLite最快跑通MySQL远程库配置实录2.1 SQLite连接代码不到十行就能跑我直接贴自己笔记里的最小可运行示例重点部分我都加了注释#include QSqlDatabase #include QSqlQuery #include QSqlError #include QDebug bool initSqliteDb() { // 加载SQLite驱动 QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE); // 这里的路径是数据库文件路径文件不存在会自动创建 db.setDatabaseName(my_notes.db); if (!db.open()) { qDebug() open sqlite error: db.lastError().text(); return false; } // 建表 QSqlQuery query(db); bool ok query.exec(CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT, created_time DATETIME DEFAULT CURRENT_TIMESTAMP)); if (!ok) { qDebug() create table error: query.lastError().text(); return false; } return true; }这里有两个容易被忽略但很关键的细节第一个是setDatabaseName(my_notes.db)。SQLite里这个路径可以写相对路径也可以写绝对路径如果路径不存在SQLite会尝试创建文件。但在实际项目中我建议把数据库文件放到用户数据目录而不是写死在程序目录里否则程序没有写权限时open()会失败。用QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)去拿目录会更稳妥。第二个是QSqlQuery query(db)。如果你在构造函数里没有传入db它会使用默认连接。当程序里有多个连接时容易出现查询打到错误连接上的问题。所以我一般都会显式传入连接对象虽然多写一点但排查问题的时候会很省心。2.2 MySQL连接配置驱动插件与连接参数的细节MySQL和SQLite完全不同它需要你先有一个MySQL服务端并且Qt这边还要能找到对应版本的驱动插件。这一步是很多人卡住的地方。先看连接代码#include QSqlDatabase #include QSqlError #include QDebug bool initMysqlDb() { QSqlDatabase db QSqlDatabase::addDatabase(QMYSQL); db.setHostName(127.0.0.1); db.setPort(3306); db.setDatabaseName(qt_demo); db.setUserName(qt_user); db.setPassword(your_password); // 指定连接编码避免中文乱码 db.setConnectOptions(MYSQL_OPT_RECONNECT1); if (!db.open()) { qDebug() mysql open error: db.lastError().text(); return false; } return true; }这段代码看着简单实际上坑都在看不见的地方QMYSQL驱动插件和Qt版本必须匹配。如果你用的是Qt自带的预编译版本那MySQL的驱动插件不一定带全。判断方法很简单跑一下qDebug() QSqlDatabase::drivers();如果列表里没有QMYSQL那就是插件缺失。解决办法一般是去Qt安装目录的plugins/sqldrivers下看有没有qsqlmysql.dll或libqsqlmysql.so。没有的话需要自己用源码编译驱动或者换一个带完整插件的Qt发行版。MySQL 8.0的认证插件问题。新版MySQL默认使用caching_sha2_password认证而旧版Qt的QMYSQL驱动可能只支持mysql_native_password。这时候你会看到类似Authentication plugin caching_sha2_password cannot be loaded的报错。处理方式有两个要么在MySQL里为这个用户改成mysql_native_password要么用更新版本的Qt。考虑到安全性和兼容性我建议尽量升级Qt和驱动。连接编码。MySQL连接时如果不对中文数据存进去读出来就可能变成乱码。一般打开数据库后可以执行SET NAMES utf8mb4或者在表设计时统一使用utf8mb4字符集。Qt侧连接选项里也可以配合设置字符集参数。2.3 连接建立后先验证环境再写业务不要在还没确认连接是否正常的时候就直接开始写CRUD。我的习惯是每次新环境测试先跑一个最小验证程序QSqlDatabase db QSqlDatabase::database(qt_sql_default_connection); if (!db.isValid()) { qDebug() no valid db connection; return; } if (!db.isOpen()) { qDebug() database not open; return; } QSqlQuery query; bool ok query.exec(SELECT VERSION()); if (ok query.next()) { qDebug() db version: query.value(0).toString(); }这个验证程序能一次性看出驱动是否加载成功、连接是否有效、查询通路是否顺畅。如果这里都通了后面写业务代码会踏实很多。3. 数据库操作的三板斧原生SQL、模型类与事务的处理逻辑连接建好之后真正写业务时主要会用到三种风格直接用QSqlQuery执行SQL语句、通过QSqlTableModel做表模型映射、以及用QSqlQueryModel做只读查询模型。下面的内容我会把三者的使用场景和选择逻辑讲清楚。3.1 QSqlQuery最灵活也最容易写错QSqlQuery是最接近原生数据库编程的API你可以拼任意合法的SQL字符串、执行带占位符的预编译语句、遍历结果集。增删改查基本就是exec一条SQL的事// 插入一条数据 QSqlQuery query; query.prepare(INSERT INTO notes (title, content) VALUES (?, ?)); query.addBindValue(Qt数据库学习); query.addBindValue(这是学习Qt数据库的第一天); if (!query.exec()) { qDebug() insert error: query.lastError().text(); }这里有一个我认为是Qt新手最容易忽略的点为什么推荐用prepare bindValue而不是直接把字符串拼接进来第一个原因是安全。如果的值来自用户输入比如一个QLineEdit的文本直接拼进SQL里一旦出现引号、百分号等特殊字符轻则SQL语法错误重则被人利用做SQL注入。第二个原因是性能很多数据库对预编译语句会做执行计划缓存反复执行同一结构、不同参数的SQL时效率明显高于一次性拼接的执行。第三个原因是代码可读性参数和SQL模板分离改动起来也更安全。查询结果集的遍历也很直白QSqlQuery query; query.exec(SELECT id, title, content FROM notes); while (query.next()) { int id query.value(0).toInt(); QString title query.value(1).toString(); QString content query.value(2).toString(); qDebug() id title content; }需要注意QSqlQuery::value()返回的是QVariant你几乎可以不关心数据库里存的实际类型直接按业务需求转成int、QString、double等即可。这层QVariant封装其实是Qt数据库模块非常好用的设计比原生API里整天对付类型转换舒服太多。3.2 QSqlTableModel把数据库表直接当个视图模型用如果你的业务不需要写特别复杂的SQL只是想把某张表展示在界面上或者做简单的增删改QSqlTableModel是一个效率极高的选择。#include QSqlTableModel QSqlTableModel *model new QSqlTableModel(this, db); model-setTable(notes); model-setFilter(id 0); model-setSort(0, Qt::AscendingOrder); model-select();setFilter相当于SQL里的WHERE子句但注意不要带WHERE关键字。setSort设置排序字段。一旦调用select()这个model里就会映射出表中符合条件的记录。你可以直接把它和QTableView绑定QTableView *view new QTableView; view-setModel(model); view-show();表格呈现出来之后你甚至可以在界面里直接编辑单元格然后调用model-submitAll()将修改提交到数据库。这个模型-视图分离的机制让CRUD的代码量一下减少很多。当然它的代价是SQL能力受限连表、聚合、子查询这些操作它做不了也没必要硬用。3.3 事务批量操作的保护伞在Qt里事务操作并不复杂核心是QSqlDatabase的三个方法bool ok db.transaction(); // 开启事务 if (!ok) return; // 执行多条SQL操作 QSqlQuery query; query.exec(UPDATE notes SET titlet1 WHERE id1); query.exec(UPDATE notes SET contentc1 WHERE id1); // 判断是否需要回滚 if (needRollback) { db.rollback(); } else { db.commit(); }事务能保证一组操作要么全部成功、要么全部失败。比如你要批量insert一万条数据每一条都单独commit的话耗时可能非常感人但如果包在一个事务里提交速度能提升一个数量级。这里要特别提一个SQLite的坑SQLite默认是自动提交模式的。如果你不主动调用transaction()每条SQL都会被当成独立事务立即提交。一旦中途出错已执行的无法回滚。所以做批量操作时务必养成先transaction()再操作的习惯。而MySQL的InnoDB引擎虽然默认也开启自动提交但事务边界会更清晰因为DDL、DML的行为差异比较大认真理解commit和rollback的执行时机才能避免逻辑错误。4. 连接数据库最容易翻车的几个场景驱动缺失、中文乱码、连接泄漏、跨线程使用这部分是我个人踩坑记的浓缩版。网上教程很少会把这些问题写全但实际项目里十有八九会遇到。4.1 驱动加载失败报driver not loaded这是我见到的最高频问题尤其出现在MySQL上。排查链路如下先确认识别的驱动列表里有没有目标驱动。执行qDebug() QSqlDatabase::drivers();。如果列表里没有检查Qt安装目录的plugins/sqldrivers是否包含对应插件文件。检查插件文件是否和你使用的Qt库版本匹配。比较隐蔽的问题是编译器位数不一致比如程序是32位编译插件是64位的加载就会失败。检查插件文件依赖的第三方库是否存在。MySQL驱动插件通常会依赖MySQL客户端库如果Qt运行时找不到那个动态库插件仍然加载不出来。处理办法不复杂要么去Qt对应发行版官网找完整的sqldrivers包要么自己编译驱动插件要么换用ODBC驱动作为备选。我个人的建议是如果允许MySQL方案下优先用最新稳定版Qt因为驱动插件和连接参数的支持都更完善可以节省很多折腾时间。4.2 中文数据乱码不是Qt单方面的问题乱码问题的排查难度在于它涉及数据库字符集、客户端编码、Qt字符串编码三个环节。你只检查一段代码往往看不出结果。我处理这类问题的固定套路是数据库表字段和表本身使用utf8mb4字符集。连接建立后先执行SET NAMES utf8mb4确保客户端连接传输编码和数据库一致。Qt侧读出来的QString如果仍然乱码检查原始字节是不是被其他环节转成了GBK或Latin1必要时用QString::fromUtf8()或fromLocal8Bit()做转换。在SQLite上只要程序里源文件编码统一为UTF-8基本不会出现乱码。MySQL的乱码概率高很多但绝大多数都能通过统一字符集解决。写数据库代码时我的建议是永远不要在代码里写死编码转换逻辑而是从连接和建表阶段就把字符集统一好。4.3 连接泄漏反复addDatabase却不closeaddDatabase()每次调用本质上是在Qt的数据库连接管理器中注册一个新连接。如果你在一个循环里重复添加同一个连接名Qt会警告并替换掉旧连接。旧连接在替换时可能会被立即释放但如果数据库连接还持有未提交的事务这里就会出现资源未被正确清理的问题。正确做法是应用启动阶段完成连接注册后续所有代码通过QSqlDatabase::database()按连接名获取不重复add。程序退出时可以用QSqlDatabase::removeDatabase()清理但注意要在所有关联的QSqlQuery销毁之后再remove否则会报连接仍在使用。我实际写代码时喜欢用一个专门类封装数据库初始化和关闭逻辑连接名定义成静态常量尽可能避免散落各处的addDatabase调用。4.4 跨线程使用同一个连接Qt这个限制尤其要重视Qt的QSqlDatabase连接默认情况下不能跨线程使用。具体表现是你在主线程打开了一个连接然后在工作线程里调用QSqlQuery执行查询要么报错要么行为诡异。正确的处理方式有两种每个线程维护自己的连接即在工作线程的初始化逻辑里重新addDatabase连接名可以不同指向同一个数据库也没问题。用Qt的信号槽机制在工作线程里完成数据库操作后通过信号把结果传回主线程。我的建议是谁创建连接谁使用连接。跨线程问题不是靠加锁能简单解决的因为内部实现可能涉及线程局部存储。与其在错误的方向上使劲不如直接从设计上规避。5. 把数据接到界面上一个TableView实例的拆解前四节讲的都是数据库操作本身但Qt数据库模块真正强大的地方在于它和Model/View框架的天然配合。我用笔记项目里的实际界面逻辑举个例子这个例子对新手非常友好。5.1 需求场景我正在写一个本地笔记管理界面左侧是标题列表右侧是详情编辑区。数据存在SQLite表notes里字段为id、title、content、created_time。我的目标是界面启动后自动加载全部笔记标题点击某一行时右侧显示对应内容新增、修改后能刷新列表。5.2 model的初始化在界面构造函数里我初始化了model并绑定到QTableViewm_model new QSqlTableModel(this); m_model-setTable(notes); m_model-setEditStrategy(QSqlTableModel::OnManualSubmit); m_model-select(); m_tableView-setModel(m_model); m_tableView-setSelectionBehavior(QAbstractItemView::SelectRows); m_tableView-setSelectionMode(QAbstractItemView::SingleSelection);setEditStrategy(QSqlTableModel::OnManualSubmit)的意思是界面上的编辑不会立即提交数据库而是等我确认后再调用submitAll()。这个策略的优点是避免误操作直接改了库里的数据缺点是你必须记得手动提交。5.3 点击行加载详情连接QTableView的clicked信号connect(m_tableView, QTableView::clicked, this, [this](const QModelIndex index) { int row index.row(); QString title m_model-data(m_model-index(row, 1)).toString(); QString content m_model-data(m_model-index(row, 2)).toString(); m_titleEdit-setText(title); m_contentEdit-setPlainText(content); });这种方式比自己去执行SQL再逐行匹配要轻松很多。因为model已经映射了整张表行的索引和数据库记录是一一对应的。5.4 新增、修改与提交新增时我直接往model尾部插一行空记录int row m_model-rowCount(); m_model-insertRow(row); m_model-setData(m_model-index(row, 1), m_titleEdit-text()); m_model-setData(m_model-index(row, 2), m_contentEdit-toPlainText()); if (!m_model-submitAll()) { qDebug() submit error: m_model-lastError().text(); m_model-revertAll(); }修改时只需对当前行的字段重新setData再submitAll。删除时用removeRow(row)同样需要submit。结合下来你会发现QSqlTableModel把数据库几乎变成了一个和QStringListModel一样好用的数据源模型开发效率提升非常明显。当然这只是在单表CRUD场景下成立一旦要做连表查询或者复杂统计还是老老实实回到QSqlQuery。5.5 模型与视图的边界什么时候用哪种方式我做了个简单取舍表放在笔记里提醒自己场景推荐方式原因简单列表展示和编辑QSqlTableModel代码量小和view天然配合复杂SQL查询、统计、报表QSqlQuery 自定义Model灵活度高SQL能力完整只读查询结果展示QSqlQueryModel快速填充数据语法简洁需要事务保护的多表操作QSqlQuery transaction保证数据一致性表模型不适合6. 最后再聊几句实战体会如果问这段时间调试Qt数据库最深的感受我的答案是先把数据库本身搞熟再来用Qt的封装就不会慌。很多人遇到问题习惯在网上搜Qt的魔改技巧但我发现80%的数据库连接问题其实是通用的数据库基础知识没打好——字符集、事务隔离、连接生命周期这些概念搞清楚后Qt层的API只是表象工具。还有一个具体的小技巧调试数据库代码时尽量打印db.lastError()和query.lastError()的完整错误信息很多问题光靠猜是猜不出来的。尤其MySQL的报错信息非常明确照着提示改一般都能定位到根因。这套笔记写到这里Qt中数据库连接与使用的基本脉络已经完整了。从SQLite起步到MySQL的驱动配置再到QSqlQuery、QSqlTableModel和事务处理最后用TableView把数据展示到界面上整个过程是一个能直接照着做的完整链路。你可以在自己的机器上跟着走一遍甚至不需要先建好数据库服务端直接用SQLite那份代码跑通以后再回头对付MySQL就能明显感受到Qt的抽象设计有多省事。

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

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

免费获取报价 →
↑