Skip to content

Commit c3173d0

Browse files
committed
[重构SqliteWAL示例并增强其功能]: 重命名类以更明确地表示针对SQLite,增加多线程下的读写操作示例,优化SQLite配置以提高性能,并增加连接管理
- 重命名文件和数据类: 将`databasetest`和`databaseutils`改为`sqlitetest`和`sqliteutils`,以更明确地表示针对SQLite数据库 - 更新README说明: 增加官方文档链接,强调最佳工程实践(写入操作使用QMutex串行化,读取操作不需要加锁) - 增强SqliteTest类功能: 增加`readLastRecord`方法用于读取最后一条记录,使用`std::call_once`确保数据库文件只删除一次,改进表创建语句(使用IF NOT EXISTS) - 优化SQLite配置: 在`sqliteutils.cc`中增加多个PRAGMA设置(如WAL模式、自动VACUUM、内存临时表等)以提升性能,并增加在连接关闭时的清理操作(如分析、优化、VACUUM等) - 增加日志输出: 输出SQLite版本、编译选项和驱动特性,便于调试和了解运行环境 - 改进连接管理: 引入连接计数机制,确保在最后一个连接关闭时执行清理操作并移除数据库连接
1 parent 46796cf commit c3173d0

11 files changed

Lines changed: 344 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: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 IF NOT EXISTS [%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+
QSqlQuery query(db);
56+
if (!query.exec(createTable)) {
57+
qWarning() << query.lastError().text();
58+
return false;
59+
}
60+
return true;
61+
}
62+
63+
SqliteTest *q_ptr;
64+
65+
SqliteConnection dataBaseConnection;
66+
const QString tableName = "phone";
67+
68+
static QMutex mutex;
69+
};
70+
71+
QMutex SqliteTest::SqliteTestPrivate::mutex;
72+
73+
SqliteTest::SqliteTest(QObject *parent)
74+
: QObject{parent}
75+
, d_ptr(new SqliteTestPrivate(this))
76+
{}
77+
78+
SqliteTest::~SqliteTest() {}
79+
80+
bool SqliteTest::insert(const QString &brand, int num)
81+
{
82+
auto db = getDatabase(d_ptr->dataBaseConnection);
83+
CHECK_DATABASE_VALIDITY(db)
84+
QSqlQuery query(db);
85+
query.prepare(
86+
QString("INSERT INTO %1 (brand, num) VALUES (:brand, :num)").arg(d_ptr->tableName));
87+
query.bindValue(":brand", brand);
88+
query.bindValue(":num", num);
89+
90+
QMutexLocker locker(&d_ptr->mutex);
91+
if (!query.exec()) {
92+
qCritical() << query.lastError().text();
93+
return false;
94+
}
95+
qInfo() << "Last inserted id:" << query.lastInsertId().toInt();
96+
return true;
97+
}
98+
99+
bool SqliteTest::readLastRecord()
100+
{
101+
auto db = getDatabase(d_ptr->dataBaseConnection);
102+
CHECK_DATABASE_VALIDITY(db)
103+
104+
QSqlQuery query(db);
105+
if (!query.exec(QString("SELECT id, brand, num, create_time, local_time "
106+
"FROM %1 ORDER BY id DESC LIMIT 1")
107+
.arg(d_ptr->tableName))) {
108+
qCritical() << query.lastError().text();
109+
return false;
110+
}
111+
if (!query.next()) {
112+
qWarning() << "No records found.";
113+
return false;
114+
}
115+
auto text = QString(
116+
"Last Record - ID: %1, Brand: %2\t, Num: %3\t, Created At: %4, Local Time: %5")
117+
.arg(QString::number(query.value("id").toInt()),
118+
query.value("brand").toString(),
119+
QString::number(query.value("num").toInt()),
120+
query.value("create_time").toString(),
121+
query.value("local_time").toString());
122+
qDebug().noquote() << text;
123+
return true;
124+
}

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)