1. 灾难现场
在开发一款本地记事本应用时,我们使用了Qt自带的QSqlDatabase (SQLite) 来存储数据。
代码在Windows上运行得行云流水,但在鸿蒙真机上测试时,出现了两个严重问题:
- 无法创建数据库:
QSqlDatabase::open()返回 false,错误信息提示unable to open database file。 - 频繁死锁:当我们在后台线程同步数据的同时,在主线程读取列表,程序抛出
database is locked错误。
2. 陷阱一:沙箱路径
在Desktop OS上,我们可以随意在 D:/data.db 或 ./data.db 创建文件。
但在鸿蒙系统中,应用被严格限制在沙箱内。
错误代码:
db.setDatabaseName("data.db"); // 试图在当前工作目录创建
在鸿蒙应用启动时,当前工作目录(CWD)可能并不是一个可写的目录,或者是一个临时的系统路径。
正确姿势:
必须使用QStandardPaths获取应用私有数据目录。
QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
// 输出示例: /data/app/el2/100/base/com.example.note/files/
QDir dir(dataDir);
if (!dir.exists()) dir.mkpath(".");
QString dbPath = dir.filePath("note.db");
db.setDatabaseName(dbPath);
注意: 确保目录存在!QSqlDatabase不会自动创建父目录。
3. 陷阱二:并发死锁 (Database is locked)
SQLite默认使用回滚日志(Journal)模式,在这种模式下,写操作是独占的。当Qt主线程在读取(Select),而后台线程试图写入(Insert)时,就会发生冲突。
在鸿蒙的文件系统上,IO性能可能不如PC SSD,导致锁等待时间变长,更容易触发 busy 错误。
锁等待示意图
4. 解决方案:开启WAL模式
WAL (Write-Ahead Logging) 模式允许读写并发。读操作不再阻塞写操作,写操作也不阻塞读操作。
代码实现:
在打开数据库后,立即执行Pragma指令。
bool initDatabase() {
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(getDbPath());
if (!db.open()) {
qCritical() << "Open failed:" << db.lastError();
return false;
}
// 开启WAL模式
QSqlQuery query;
if (!query.exec("PRAGMA journal_mode = WAL")) {
qWarning() << "Failed to set WAL mode";
} else {
if (query.next()) {
qDebug() << "Journal mode set to:" << query.value(0).toString();
}
}
// 增加忙等待超时 (默认可能很短)
query.exec("PRAGMA busy_timeout = 5000"); // 5秒
return true;
}
5. 架构优化:单例模式管理连接
在多线程环境使用QSqlDatabase,Qt文档建议每个线程使用不同的连接名(Connection Name)。
但为了管理方便,我们建议封装一个线程安全的单例类。
// DBManager.h
class DBManager {
public:
static DBManager& instance();
// 获取当前线程的数据库连接
QSqlDatabase getDatabase();
private:
// 线程本地存储,确保每个线程有独立的连接名
static thread_local QString m_connectionName;
};
// DBManager.cpp
thread_local QString DBManager::m_connectionName;
QSqlDatabase DBManager::getDatabase() {
if (m_connectionName.isEmpty()) {
// 生成唯一连接名,如 "conn_0x1234abcd"
m_connectionName = QString("conn_%1").arg((quintptr)QThread::currentThreadId());
}
if (!QSqlDatabase::contains(m_connectionName)) {
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", m_connectionName);
db.setDatabaseName(getGlobalDbPath());
db.open();
// Set WAL...
}
return QSqlDatabase::database(m_connectionName);
}
6. 总结
在鸿蒙上使用SQLite,核心要点只有三个:
- 路径对不对:必须用
QStandardPaths::AppDataLocation。 - 模式开没开:强烈建议开启
WAL模式提升并发性能。 - 连接管没管:多线程必须用不同的连接名,切忌跨线程共用
QSqlDatabase对象。
做好了这三点,SQLite在鸿蒙上依然是那个轻量、可靠的数据存储王者。
5629

被折叠的 条评论
为什么被折叠?



