Skip to content

Commit 6bb5d24

Browse files
committed
[SqliteWAL]: 重构示例并优化SQLite配置
- 文件重命名: 将`databasetest`和`databaseutils`统一重命名为`sqlitetest`和`sqliteutils`,保持命名一致性 - 文档更新: 替换README中WAL模式说明为官方文档链接,明确工程实践方案 - 功能增强: - 增加`readLastRecord()`方法支持读取最后插入的记录 - 添加`local_time`字段存储本地格式化时间戳 - SQL优化: - 增加WAL模式下的PRAGMA优化配置(内存映射/自动清理/缓存设置) - 添加SQLite版本及编译选项的运行时日志输出 - 实现数据库连接关闭时的主动优化操作(ANALYZE/VACUUM) - 线程安全: - 明确写入操作使用QMutex串行化避免SQLITE_BUSY错误 - 读取操作无需加锁保证并发性能 - 示例扩展: - 增加测试手机品牌数量(9个品牌) - 每个写入线程增加读取最后记录操作 - 健壮性提升: - 数据库连接移除时增加资源清理 - 首次运行时删除旧测试数据库 - 增强错误日志输出
1 parent 46796cf commit 6bb5d24

11 files changed

Lines changed: 305 additions & 174 deletions

README.md

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,19 +150,12 @@
150150
151151
## [SqliteWAL](SqliteWAL/)——Sqlite WAL 模式下多线程并发写入数据库程序
152152
153-
1. 每个线程拥有独立的数据库连接(不同的连接名称),线程退出需要主动移除数据库连接,不然会产生大量的数据库连接;
154-
2. 在多线程下,依旧使用QMutex保证线程安全,在读的时候,可以考虑不使用QMutex,并发读应该没什么影响(对于写的时候,可以考虑使用QMutex);
155-
156-
### WAL模式的优点
157-
158-
1. 提高了并发性:WAL模式允许多个读取器和一个写入器同时访问数据库,可以提高并发性能;
159-
2. 崩溃恢复:WAL模式在发生崩溃时确保数据库保持一致,通过在提交事务之前将所有更改刷新到日志文件来实现;
153+
[Write-Ahead Logging (WAL) 官方文档](https://sqlite.org/wal.html)
160154
161-
### WAL模式的注意事项
162-
163-
1. WAL模式仅适用于SQLite 3.35.5+版本;
164-
2. 增加了磁盘使用量:与回滚模式相比,WAL模式需要更多的磁盘空间,因为它在提交更改之前将所有更改都写入日志文件;
165-
3. 读取性能较慢:读取操作不会被写入操作阻塞,如果同时进行读取和写入操作,可能导致数据不一致。
155+
1. 每个线程拥有独立的数据库连接(不同的连接名称),线程退出需要主动移除数据库连接,不然会产生大量的数据库连接;
156+
2. 最佳工程实践,并不是最佳性能:
157+
1. 写入操作使用`QMutex`,使写入操作串行化,避免[SQLITE_BUSY](https://sqlite.org/rescode.html#busy)错误;
158+
2. 读取操作不需要加锁;
166159
167160
## [TableViewModel](TableViewModel/)——表格视图
168161

SqliteWAL/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
set(PROJECT_SOURCES databasetest.cc databasetest.hpp databaseutils.cc
2-
databaseutils.hpp main.cc)
1+
set(PROJECT_SOURCES sqlitetest.cc sqlitetest.hpp sqliteutils.cc sqliteutils.hpp
2+
main.cc)
33

44
qt_add_executable(SqliteWAL MANUAL_FINALIZATION ${PROJECT_SOURCES})
55
target_link_libraries(SqliteWAL PRIVATE Qt::Core Qt::Sql)

SqliteWAL/SqliteWAL.pro

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ QT += core sql
55
CONFIG += cmdline
66

77
SOURCES += \
8-
databasetest.cc \
9-
databaseutils.cc \
10-
main.cc
8+
main.cc \
9+
sqlitetest.cc \
10+
sqliteutils.cc
1111

1212
HEADERS += \
13-
databasetest.hpp \
14-
databaseutils.hpp
13+
sqlitetest.hpp \
14+
sqliteutils.hpp
1515

1616
# Default rules for deployment.
1717
qnx: target.path = /tmp/$${TARGET}/bin

SqliteWAL/databasetest.cc

Lines changed: 0 additions & 95 deletions
This file was deleted.

SqliteWAL/databasetest.hpp

Lines changed: 0 additions & 17 deletions
This file was deleted.

SqliteWAL/databaseutils.cc

Lines changed: 0 additions & 37 deletions
This file was deleted.

SqliteWAL/main.cc

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#include "databasetest.hpp"
1+
#include "sqlitetest.hpp"
22

33
#include <QCoreApplication>
44
#include <QSqlDatabase>
@@ -7,9 +7,10 @@
77

88
void insertThread(const QString &brand)
99
{
10-
DataBaseTest test;
10+
SqliteTest test;
1111
for (int i = 0; i < 1000; i++) {
1212
test.insert(brand, i);
13+
test.readLastRecord();
1314
}
1415
}
1516

@@ -22,7 +23,16 @@ int main(int argc, char *argv[])
2223
return -1;
2324
}
2425

25-
const QStringList brands{"Apple", "Samsung", "Xiaomi", "Huawei", "Oppo", "Vivo", "Realme"};
26+
const QStringList brands{"Apple",
27+
"Samsung",
28+
"Xiaomi",
29+
"Redmi",
30+
"Oppo",
31+
"Vivo",
32+
"Realme",
33+
"IQOO",
34+
"OnePlus",
35+
"Pixel"};
2636
std::vector<std::thread> threads;
2737
for (const auto &brand : std::as_const(brands)) {
2838
threads.emplace_back(insertThread, brand);

SqliteWAL/sqlitetest.cc

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#include "sqlitetest.hpp"
2+
#include "sqliteutils.hpp"
3+
4+
#include <QDir>
5+
#include <QMutex>
6+
#include <QSqlError>
7+
#include <QSqlQuery>
8+
#include <QThread>
9+
10+
#include <mutex>
11+
12+
class SqliteTest::SqliteTestPrivate
13+
{
14+
public:
15+
explicit SqliteTestPrivate(SqliteTest *q)
16+
: q_ptr(q)
17+
{
18+
dataBaseConnection.dataBasePath = QString("%1/%2").arg(QDir::tempPath()).arg("test.db");
19+
20+
static std::once_flag onceFlag;
21+
std::call_once(onceFlag, [this]() { QFile::remove(this->dataBaseConnection.dataBasePath); });
22+
23+
dataBaseConnection.connectionName = getDatabaseConnectionName();
24+
25+
createTable();
26+
27+
qInfo() << "DataBaseTestPrivate connectionName: " << dataBaseConnection.connectionName
28+
<< QThread::currentThread();
29+
}
30+
31+
~SqliteTestPrivate()
32+
{
33+
QMutexLocker locker(&mutex);
34+
removeDatabase(dataBaseConnection);
35+
}
36+
37+
bool createTable()
38+
{
39+
const auto createTable
40+
= QString(
41+
"CREATE TABLE [%1]("
42+
" [id] INTEGER NOT NULL ON CONFLICT REPLACE UNIQUE ON CONFLICT "
43+
"REPLACE COLLATE BINARY, "
44+
" [brand] TEXT, "
45+
" [num] TEXT, "
46+
" [create_time] TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "
47+
" [local_time] TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now', 'localtime')), "
48+
" PRIMARY KEY([id] COLLATE [BINARY] ASC) ON CONFLICT REPLACE)")
49+
.arg(tableName);
50+
51+
QMutexLocker locker(&mutex);
52+
auto db = getDatabase(dataBaseConnection);
53+
CHECK_DATABASE_VALIDITY(db)
54+
55+
if (db.tables().contains(tableName)) {
56+
return true;
57+
}
58+
59+
QSqlQuery query(db);
60+
if (!query.exec(createTable)) {
61+
qWarning() << query.lastError().text();
62+
return false;
63+
}
64+
return true;
65+
}
66+
67+
SqliteTest *q_ptr;
68+
69+
SqliteConnection dataBaseConnection;
70+
const QString tableName = "phone";
71+
72+
static QMutex mutex;
73+
};
74+
75+
QMutex SqliteTest::SqliteTestPrivate::mutex;
76+
77+
SqliteTest::SqliteTest(QObject *parent)
78+
: QObject{parent}
79+
, d_ptr(new SqliteTestPrivate(this))
80+
{}
81+
82+
SqliteTest::~SqliteTest() {}
83+
84+
bool SqliteTest::insert(const QString &brand, int num)
85+
{
86+
auto db = getDatabase(d_ptr->dataBaseConnection);
87+
CHECK_DATABASE_VALIDITY(db)
88+
QSqlQuery query(db);
89+
query.prepare(
90+
QString("INSERT INTO %1 (brand, num) VALUES (:brand, :num)").arg(d_ptr->tableName));
91+
query.bindValue(":brand", brand);
92+
query.bindValue(":num", num);
93+
94+
QMutexLocker locker(&d_ptr->mutex);
95+
if (!query.exec()) {
96+
qCritical() << query.lastError().text();
97+
return false;
98+
}
99+
qInfo() << "Last inserted id:" << query.lastInsertId().toInt();
100+
return true;
101+
}
102+
103+
bool SqliteTest::readLastRecord()
104+
{
105+
auto db = getDatabase(d_ptr->dataBaseConnection);
106+
CHECK_DATABASE_VALIDITY(db)
107+
108+
QSqlQuery query(db);
109+
if (!query.exec(QString("SELECT id, brand, num, create_time, local_time "
110+
"FROM %1 ORDER BY id DESC LIMIT 1")
111+
.arg(d_ptr->tableName))) {
112+
qCritical() << query.lastError().text();
113+
return false;
114+
}
115+
if (!query.next()) {
116+
qWarning() << "No records found.";
117+
return false;
118+
}
119+
auto text = QString(
120+
"Last Record - ID: %1, Brand: %2\t, Num: %3\t, Created At: %4, Local Time: %5")
121+
.arg(QString::number(query.value("id").toInt()),
122+
query.value("brand").toString(),
123+
QString::number(query.value("num").toInt()),
124+
query.value("create_time").toString(),
125+
query.value("local_time").toString());
126+
qDebug().noquote() << text;
127+
return true;
128+
}

SqliteWAL/sqlitetest.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#pragma once
2+
3+
#include <QObject>
4+
5+
class SqliteTest : public QObject
6+
{
7+
Q_OBJECT
8+
public:
9+
explicit SqliteTest(QObject *parent = nullptr);
10+
~SqliteTest();
11+
12+
bool insert(const QString &brand, int num);
13+
bool readLastRecord();
14+
15+
private:
16+
class SqliteTestPrivate;
17+
QScopedPointer<SqliteTestPrivate> d_ptr;
18+
};

0 commit comments

Comments
 (0)