尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Qt数据库编程入门:从QSqlDatabase到SQLite驱动部署全解析

发布时间:2026/9/26 20:45:57

资讯中心
01
ARTICLE

Qt数据库编程入门:从QSqlDatabase到SQLite驱动部署全解析

Qt数据库编程入门:从QSqlDatabase到SQLite驱动部署全解析
如果你和我一样是抱着《Qt Creator快速入门第三版》从第16章翻到第17章多半会在“数据库”这三个字前面停下来。前十六章都在讲界面、绘图和事件到了数据库这一章突然换了一套词QSqlDatabase、QSqlQuery、驱动、连接……我当年照着书敲第一段代码只花了十分钟但真正明白这一章在讲什么却花了两个晚上。这篇把第17-1章的脉络、示例代码和书里没展开的实操要点一起整理出来。适合刚看完理论、准备动手写第一个带数据库的Qt程序的人也适合那些在本机能跑、换台机器就报“driver not loaded”的读者。1. 第17-1章的知识边界Qt SQL不是“一个类”而是一套分层结构1.1 教材这一节到底写了什么第三版把数据库独立成第17章17-1是这一章的第一节定位是“数据库编程入门”。它的目标很明确让读者学会用QSqlDatabase建立连接用QSqlQuery执行SQL。示例数据库选的是SQLite这个选择非常合理——SQLite不需要安装服务端一个.db文件就是整个数据库拿来理解连接、表、SQL语句的执行流程最直接。书里给出的大致主线是这样的在.pro文件里加sql模块调用QSqlDatabase::addDatabase设置数据库文件路径open打开连接然后用QSqlQuery执行建表、插入、查询。这条主线没有错但它把数据库访问简化成了一个“三步走”。等你真正遇到多线程、换数据库、发布部署问题时会发现教材没讲的部分才最容易卡人。1.2 真正要理解的四个层次在我的理解里Qt SQL不是某一个类而是拆成了四层结构。理解这四层比背API更重要。层次关键类职责驱动层QSqlDriver、QSqlDriverCreator真正和数据库通信的插件比如QSQLITE、QMYSQL连接层QSqlDatabase管理一个数据库连接保存连接参数提供open、close、事务查询层QSqlQuery执行SQL语句、绑定参数、遍历结果集模型层QSqlTableModel、QSqlQueryModel把查询结果封装成能直接给视图使用的数据模型打个比方驱动层像网卡驱动连接层像网线接口查询层像你发出的网络请求模型层像浏览器渲染好的页面。只学会用QSqlQuery发请求当然能干活但一旦网卡没装好、接口被复用、页面渲染卡顿你就得知道问题出在哪一层。1.3 连接名与addDatabase的正确姿势书上的例子大多这样写QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE);不传第二个参数用的就是“默认连接”。单连接场景下很省事但同一个进程里同时打开业务库和日志库时这种写法就会出问题第二次调用addDatabase会覆盖默认连接或者直接报“duplicate connection name”。正确做法是给每个连接起名字QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE, order_db); db.setDatabaseName(order.db); QSqlDatabase logger QSqlDatabase::addDatabase(QSQLITE, log_db); logger.setDatabaseName(log.db);释放连接时也不能直接删对象。因为QSqlQuery还持有这个连接的引用正确顺序是先让所有QSqlQuery析构再close最后removeDatabase{ QSqlDatabase db QSqlDatabase::database(order_db, false); if (db.isOpen()) { db.close(); } } QSqlDatabase::removeDatabase(order_db);这个坑在Qt官方文档里写得很明确但很少出现在入门教材里。长期运行的Qt程序如果不注意释放顺序Qt会在控制台打出一堆“connection is still in use”的警告日积月累还会出现连接泄漏。2. 动手搭SQLite示例从.pro到增删改查完整跑通2.1 工程配置少一行QT sql就白搭新建一个控制台工程第一件事是在.pro文件里加上sql模块QT sql CONFIG console CONFIG - app_bundle TEMPLATE app TARGET dbtest SOURCES main.cpp如果将来要用到界面控件再补QT widgets。有人会问为什么要单独加sql模块Qt把功能拆成了模块core、gui、widgets、network、sql各自独立。加上sql才会把QSqlDatabase、QSqlQuery这些类链接进程序不加的话编译阶段就会报“找不到头文件”或者“未定义引用”。2.2 建库建表SQLite的“数据库即文件”特性SQLite最讨喜的地方就是一个文件就是整个数据库。不需要安装服务不需要配端口setDatabaseName传一个路径open就会自动创建这个文件。下面是一段能直接跑通的最小示例#include QCoreApplication #include QSqlDatabase #include QSqlQuery #include QSqlError #include QVariant #include QDebug int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE, demo); db.setDatabaseName(demo.db); if (!db.open()) { qCritical() 打开数据库失败: db.lastError().text(); return 1; } { QSqlQuery query(db); bool ok query.exec(CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER)); if (!ok) { qCritical() 建表失败: query.lastError().text(); } } db.close(); QSqlDatabase::removeDatabase(demo); return 0; }注意这里我把QSqlQuery包在一对花括号里就是为了保证在removeDatabase之前QSqlQuery对象已经析构。代码这么写可能比书上“长”了一点但它是规范的资源管理顺序建议从第一课就养成这个习惯。2.3 增删改查全套闭环建好表之后把插入、查询、修改、删除都跑一遍。我用的是绑定参数的写法后面会解释为什么比字符串拼接更安全{ QSqlQuery query(db); // 方便重复运行先清空表 query.exec(DELETE FROM user); // 插入 query.prepare(INSERT INTO user(name, age) VALUES (?, ?)); query.addBindValue(张三); query.addBindValue(28); if (!query.exec()) { qCritical() 插入失败: query.lastError().text(); return 1; } // 再插一条 query.prepare(INSERT INTO user(name, age) VALUES (?, ?)); query.addBindValue(李四); query.addBindValue(35); query.exec(); // 修改 query.exec(UPDATE user SET age 29 WHERE name 张三); // 查询 query.exec(SELECT id, name, age FROM user ORDER BY id); while (query.next()) { int id query.value(0).toInt(); QString name query.value(1).toString(); int age query.value(2).toInt(); qDebug() id name age; } // 删除 query.exec(DELETE FROM user WHERE name 李四); }这里最容易被忽略的是query.next()的循环遍历逻辑。SELECT执行成功后结果集指针停在第一条记录之前每调用一次next()就向下移动一行返回false表示没有更多记录了。很多新手只调用一次next()只取到第一行数据就以为完事了。2.4 资源回收QSqlDatabase和QSqlQuery的析构顺序前面代码里特意保留的“多余”花括号值得再展开说一次。QSqlQuery对象内部持有连接的引用只要你还在用QSqlQuery对应连接就不能被安全移除。如果你在main函数最后直接写db.close(); QSqlDatabase::removeDatabase(demo);但此时query还活着控制台会输出类似这样的警告QSqlDatabasePrivate::removeDatabase: connection demo is still in use, all queries will cease to work.解决办法就是作用域隔离。把查询相关操作放进一个块里让QSqlQuery先析构再关闭和移除连接。这也是我在给新人做Code Review时一定会强调的点Qt的数据库连接不是普通的对象释放它和查询对象之间有隐式的生命周期依赖。3. 书里没细讲但很容易翻车的三个细节3.1 永远不要用字符串拼接SQL用prepare绑定参数很多入门资料会这么写插入query.exec(INSERT INTO user(name, age) VALUES(张三, 28));能跑但有三个隐患。第一字段值里一旦出现单引号SQL语句就被拼坏了。比如插入一个用户名叫ONeil拼出来的SQL会变成VALUES(ONeil, 28)语法直接报错。第二如果用用户输入拼SQL等于把一个执行入口交给别人经典的SQL注入就是这么来的。第三每条语句都要重新解析、生成执行计划批量插入时性能明显不如绑定参数。绑定参数的写法是query.prepare(INSERT INTO user(name, age) VALUES(?, ?)); query.addBindValue(name); query.addBindValue(age); query.exec();?是位置占位符。如果将来要迁到Oracle这类数据库可能会用到命名占位符比如:name。由于QSqlQuery的占位符风格由驱动决定跨数据库时这点要额外注意。但无论如何用prepare绑定参数比手动拼字符串稳妥得多。3.2 SQLite并发与事务批量写库卡顿怎么办SQLite默认每一条INSERT都是一个自动提交的事务。如果你在循环里插入一万条记录每一条都要经历一次磁盘同步速度会慢得让人怀疑人生。解决办法很简单把批量插入包在一个事务里if (db.transaction()) { QSqlQuery query(db); bool allOk true; for (const auto record : records) { query.prepare(INSERT INTO user(name, age) VALUES(?, ?)); query.addBindValue(record.name); query.addBindValue(record.age); if (!query.exec()) { allOk false; break; } } if (allOk) { db.commit(); } else { db.rollback(); } }除此之外SQLite在小规模并发读写下还有一个很实用的设置——WAL模式。它能让读操作和写操作不互相阻塞明显改善多线程读写的卡顿。打开方式是一条PRAGMA语句QSqlQuery pragma(db); pragma.exec(PRAGMA journal_modeWAL;); pragma.exec(PRAGMA busy_timeout5000;);busy_timeout的作用是当数据库文件被其他连接锁住时SQLite最多等5秒而不是立刻报“database is locked”。这个参数在小型桌面工具里非常实用教材里基本不会写。3.3 中文乱码与编码问题Qt 6里QString在内部是UTF-16和SQLite交互时文本统一走UTF-8理论上中文不会乱。真正导致乱码的往往不是数据库而是源文件编码。Windows上如果源文件是GBK编码字符串字面量张三在编译后是一串GBK字节而QString构造时会按UTF-8去解释自然就乱码了。解决办法是让所有源文件保持UTF-8编码。Qt Creator新建的文件默认就是UTF-8但如果你从旧项目里拷贝文件或者用其他编辑器编辑过就要多检查。如果用了CMake且编译器是MSVC可以加编译选项add_compile_options($$CXX_COMPILER_ID:MSVC:/utf-8)数据库本身也要保证用UTF-8存储SQLite的TEXT字段没有强制编码但统一UTF-8能让后续对接Web前端、JSON输出时省掉大量转换问题。4. 从“本机能跑”到“换机就挂”驱动与发布部署的对应方案4.1 新版Qt里driver not loaded的几个原因换一台电脑跑程序最常见的报错是QSqlDatabase: QSQLITE driver not loaded QSqlDatabase: available drivers: QSQLITE QMYSQL QODBC ...看到“driver not loaded”时问题大多数不在代码而在运行环境。Qt的数据库驱动是以插件形式加载的发布程序时少了对应的插件文件QSqlDatabase::addDatabase(QSQLITE)就找不到驱动。常见原因有三个一是没有拷贝sqldrivers目录下的插件二是Qt运行时找不到插件路径三是MySQL这类需要额外客户端库的驱动只拷贝了Qt插件还不够。4.2 windeployqt和手动拷贝的正确流程在Windows上最省事的做法是用windeployqt工具自动部署。假设你的程序编译在build目录exe叫dbtest.exe在Qt命令行环境里执行windeployqt dbtest.exe它会自动拷贝Qt运行库、平台插件、sqldrivers目录等。执行完可以在exe目录下看到类似结构dbtest.exe platforms/ qwindows.dll sqldrivers/ qsqlite.dll qsqlmysql.dll qsqlodbc.dll如果项目用了自定义插件路径或者你想手动部署需要在程序启动时提前告诉Qt插件在哪QCoreApplication::addLibraryPath( QCoreApplication::applicationDirPath() /plugins);然后按上面的目录结构把sqldrivers放进plugins目录里。这里有个容易踩的坑只拷贝qsqlite.dll却没拷贝platforms/qwindows.dll程序能启动但窗口显示异常反过来平台插件齐全但缺少sqldrivers一执行数据库代码就报“driver not loaded”。两者缺一不可检查时不能只看一个。4.3 SQLite换成MySQL、达梦、金仓时代码要动哪几处书里的示例用的是SQLite但现实项目里经常要换成MySQL或者政企环境中常见的达梦、金仓等数据库。换库并没有想象中那么可怕重点关注的只有几处地方。数据库驱动名连接要点SQLiteQSQLITE文件路径即可无需账号密码MySQL / MariaDBQMYSQL需要服务端端口默认3306需要账号密码PostgreSQLQPSQL端口默认5432需要账号密码达梦 DM8QODBC通过ODBC驱动连接端口默认5236金仓 KingbaseESQODBC或QPSQL建议用官方ODBC兼容模式可试QPSQL代码层面连接参数的设置会变成类似这样QSqlDatabase db QSqlDatabase::addDatabase(QMYSQL, main); db.setHostName(192.168.1.10); db.setPort(3306); db.setDatabaseName(app_db); db.setUserName(app_user); db.setPassword(your_password);换成ODBC连接达梦或金仓时要用QODBC驱动并设置连接串QSqlDatabase db QSqlDatabase::addDatabase(QODBC, dm); db.setDatabaseName(Driver{DM8 ODBC DRIVER};Server127.0.0.1;Port5236;DatabaseDAMENG);这里有几个隐藏问题。第一发布时除了拷贝qsqlodbc.dll目标机器上还必须安装对应的ODBC驱动否则照样报驱动加载失败。第二MySQL 8.0之后默认认证插件是caching_sha2_password如果程序依赖的libmysql.dll版本太老会出现“连接失败认证方式不支持”的问题需要换成新版客户端库或者改用libmariadb.dll。第三不同数据库的SQL方言有差异比如分页写法、自增主键语法迁移时不能只改连接参数SQL语句也要跟着改。4.4 引申连接池和数据库同步工具的思路入门阶段不需要自己做连接池但要知道方向。QSqlDatabase的连接不能跨线程使用这句话的意思是在一个线程里创建的连接最好只在这个线程里使用。多线程并发访问数据库时比较朴素的做法是“每线程一个连接”连接名用线程ID区分QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE, QString(conn_%1).arg(reinterpret_castquint64(QThread::currentThreadId())));更复杂的高并发场景就需要封装连接池维护一组空闲连接、分配连接、回收连接。这块内容教材不会讲但如果你要面试或者接手正式项目迟早会碰到。至于数据库同步更是一个被问烂的话题。客户端直连数据库做双向同步很容易出现主键冲突、数据覆盖、断网续传等问题。我的建议是不要自己写同步引擎。优先让客户端通过业务服务端操作数据离线场景再考虑官方同步工具或基于日志的同步方案。Qt应用的定位通常是客户端保持“客户端只读写自己的数据、业务逻辑尽量后置”的思路会省很多事。5. 读完第17-1章后我建议你额外做的三个小实验5.1 用QSqlTableModel把表格直接拖到QTableView上学完QSqlQuery之后再学QSqlTableModel会打开另一个世界。它能把一张表直接映射成数据模型和QTableView绑定后几乎不用写SQL就能实现数据展示和编辑QSqlTableModel *model new QSqlTableModel(this, db); model-setTable(user); model-setEditStrategy(QSqlTableModel::OnManualSubmit); model-select(); ui-tableView-setModel(model);这个实验的意义在于理解“模型层”到底解决了什么问题。QSqlQuery是一次性的查询工具而QSqlTableModel是表结构级的抽象增删改查都被封装好了。如果将来要做带界面的管理工具这条路比手写SQL节省大量时间。注意QSqlTableModel对表的主键有依赖如果表没有主键或主键不唯一编辑提交时会遇到问题。5.2 用Qt Creator自带的Database面板调试SQL很多人不知道Qt Creator自带数据库浏览功能。打开Qt Creator左侧的Database面板或者从工具菜单里找到Databases添加一个SQLite连接就能直接查看表结构、浏览数据、执行临时SQL。这个工具在调试阶段特别顺手。比如你的程序写了一条UPDATE但数据没变你可以先在Database面板里手动执行一遍同样的SQL确认SQL本身没问题再把注意力放到程序的参数绑定和事务逻辑上。省去了一次次打开外部数据库工具的切换成本。5.3 把示例改成“每线程一个连接”的并发测试入门之后强烈建议做一个小实验写一个QThread子类在run()里创建自己的数据库连接循环插入数据看SQLite在单线程、多线程下的表现差异。实验时先不开事务再开事务对比耗时再把journal_mode改成WAL看并发读写的错误率变化。这个实验做下来你会对“数据库连接是有线程归属的”“SQLite不是完全无锁的”“事务能带来数量级的性能提升”这些话有切身体会比单纯背结论有用得多。做完之后再回到QSqlDatabase的四层结构图你会发现之前理解模糊的地方突然都串起来了。最后再分享一个我自己的使用习惯我会在工程里加一个db_connection.h的封装文件把addDatabase、open、驱动类型判断、连接名管理全部收敛到一个函数里业务代码只传数据库类型和连接串。这样后来换库、加连接池、调整发布配置改一个文件就够了。这个习惯就是从第17-1章的示例代码里延伸出来的也是我建议所有入门者尽早养成的。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。