Skip to content

Commit 5977da9

Browse files
committed
[SqliteWAL]:重命名类与文件,增加SQLite性能优化配置
- **文件重命名**:将`databasetest`和`databaseutils`统一重命名为`sqlitetest`和`sqliteutils`,提高命名一致性 - **增加读取功能**:在`SqliteTest`类中新增`readLastRecord()`方法,实现读取最后一条记录的功能 - **优化多线程处理**: - 明确写入操作需要`QMutex`锁保证线程安全 - 读取操作不加锁,充分利用WAL模式的并发读特性 - **增强数据库配置**:在`sqliteutils`中增加多项PRAGMA优化设置: - 启用内存临时表(`temp_store=MEMORY`) - 配置WAL自动检查点(`wal_autocheckpoint`) - 设置内存映射(`mmap_size`) - 禁用缓存溢出(`cache_spill=false`) - **完善资源管理**: - 程序启动时删除旧数据库文件 - 数据库连接移除前执行`ANALYZE`和`incremental_vacuum`优化 - 添加WAL检查点截断(`wal_checkpoint(TRUNCATE)`) - **扩展测试数据**:在`main`函数中增加更多手机品牌,加强并发测试 - **增加能力检测**:添加`printDriverCapabilities`函数显示SQLite驱动特性支持情况 - **更新文档说明**:在README中明确WAL模式下读写操作的锁使用策略
1 parent 46796cf commit 5977da9

10 files changed

Lines changed: 187 additions & 90 deletions

File tree

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,12 @@
148148
<img src="SlipButton/picture/SlipButton_checked.png" width="40%" height="40%">
149149
</div>
150150
151-
## [SqliteWAL](SqliteWAL/)——Sqlite WAL 模式下多线程并发写入数据库程序
151+
## [SqliteWAL](SqliteWAL/)——[Sqlite WAL](https://sqlite.org/wal.html) 模式下多线程并发写入数据库程序
152152
153153
1. 每个线程拥有独立的数据库连接(不同的连接名称),线程退出需要主动移除数据库连接,不然会产生大量的数据库连接;
154-
2. 在多线程下,依旧使用QMutex保证线程安全,在读的时候,可以考虑不使用QMutex,并发读应该没什么影响(对于写的时候,可以考虑使用QMutex);
154+
2. 多线程情况下:
155+
1. 写入操作需要使用`QMutex`,使写入操作串行化,避免`SQLITE_BUSY`错误;
156+
2. 读取操作不需要加锁,WAL模式下读取操作不会被写入操作阻塞,可以同时进行读取和写入操作,而且可以多个读取器同时访问数据库,可以提高并发性能;
155157
156158
### WAL模式的优点
157159
@@ -160,7 +162,7 @@
160162
161163
### WAL模式的注意事项
162164
163-
1. WAL模式仅适用于SQLite 3.35.5+版本
165+
1. WAL模式仅适用于`SQLite` [version 3.7.0](https://sqlite.org/releaselog/3_7_0.html) (2010-07-21) 及更高版本
164166
2. 增加了磁盘使用量:与回滚模式相比,WAL模式需要更多的磁盘空间,因为它在提交更改之前将所有更改都写入日志文件;
165167
3. 读取性能较慢:读取操作不会被写入操作阻塞,如果同时进行读取和写入操作,可能导致数据不一致。
166168

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.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);
Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1-
#include "databasetest.hpp"
2-
#include "databaseutils.hpp"
1+
#include "sqlitetest.hpp"
2+
#include "sqliteutils.hpp"
33

44
#include <QDir>
55
#include <QMutex>
6-
#include <QSqlDriver>
76
#include <QSqlError>
87
#include <QSqlQuery>
98
#include <QThread>
109

11-
class DataBaseTest::DataBaseTestPrivate
10+
#include <mutex>
11+
12+
class SqliteTest::SqliteTestPrivate
1213
{
1314
public:
14-
explicit DataBaseTestPrivate(DataBaseTest *q)
15+
explicit SqliteTestPrivate(SqliteTest *q)
1516
: q_ptr(q)
1617
{
1718
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+
1823
dataBaseConnection.connectionName = getDatabaseConnectionName();
1924

2025
createTable();
@@ -23,7 +28,7 @@ class DataBaseTest::DataBaseTestPrivate
2328
<< QThread::currentThread();
2429
}
2530

26-
~DataBaseTestPrivate() { removeDatabase(dataBaseConnection.connectionName); }
31+
~SqliteTestPrivate() { removeDatabase(dataBaseConnection); }
2732

2833
bool createTable()
2934
{
@@ -41,12 +46,6 @@ class DataBaseTest::DataBaseTestPrivate
4146
auto db = getDatabase(dataBaseConnection);
4247
CHECK_DATABASE_VALIDITY(db)
4348

44-
auto *driver = db.driver();
45-
qInfo() << "Support Transactions: " << driver->hasFeature(QSqlDriver::Transactions)
46-
<< "Support LastInsertId: " << driver->hasFeature(QSqlDriver::LastInsertId)
47-
<< "Support BatchOperations: " << driver->hasFeature(QSqlDriver::BatchOperations)
48-
<< "Support SimpleLocking: " << driver->hasFeature(QSqlDriver::SimpleLocking);
49-
5049
if (db.tables().contains(tableName)) {
5150
return true;
5251
}
@@ -59,37 +58,62 @@ class DataBaseTest::DataBaseTestPrivate
5958
return true;
6059
}
6160

62-
DataBaseTest *q_ptr;
61+
SqliteTest *q_ptr;
6362

64-
DataBaseConnection dataBaseConnection;
63+
SqliteConnection dataBaseConnection;
6564
const QString tableName = "phone";
6665

6766
static QMutex mutex;
6867
};
6968

70-
QMutex DataBaseTest::DataBaseTestPrivate::mutex;
69+
QMutex SqliteTest::SqliteTestPrivate::mutex;
7170

72-
DataBaseTest::DataBaseTest(QObject *parent)
71+
SqliteTest::SqliteTest(QObject *parent)
7372
: QObject{parent}
74-
, d_ptr(new DataBaseTestPrivate(this))
73+
, d_ptr(new SqliteTestPrivate(this))
7574
{}
7675

77-
DataBaseTest::~DataBaseTest() {}
76+
SqliteTest::~SqliteTest() {}
7877

79-
bool DataBaseTest::insert(const QString &brand, int num)
78+
bool SqliteTest::insert(const QString &brand, int num)
8079
{
81-
QMutexLocker locker(&d_ptr->mutex);
8280
auto db = getDatabase(d_ptr->dataBaseConnection);
8381
CHECK_DATABASE_VALIDITY(db)
8482
QSqlQuery query(db);
8583
query.prepare(
8684
QString("INSERT INTO %1 (brand, num) VALUES (:brand, :num)").arg(d_ptr->tableName));
8785
query.bindValue(":brand", brand);
8886
query.bindValue(":num", num);
87+
88+
QMutexLocker locker(&d_ptr->mutex);
8989
if (!query.exec()) {
9090
qCritical() << query.lastError().text();
9191
return false;
9292
}
9393
qInfo() << "Last inserted id:" << query.lastInsertId().toInt();
9494
return true;
9595
}
96+
97+
bool SqliteTest::readLastRecord()
98+
{
99+
auto db = getDatabase(d_ptr->dataBaseConnection);
100+
CHECK_DATABASE_VALIDITY(db)
101+
102+
QSqlQuery query(db);
103+
if (!query.exec(QString("SELECT id, brand, num, create_time FROM %1 ORDER BY id DESC LIMIT 1")
104+
.arg(d_ptr->tableName))) {
105+
qCritical() << query.lastError().text();
106+
return false;
107+
}
108+
if (!query.next()) {
109+
qWarning() << "No records found.";
110+
return false;
111+
}
112+
auto text = QString("Last Record - ID: %1, Brand: %2, Num: %3, Created At: %4")
113+
.arg(QString::number(query.value("id").toInt()),
114+
query.value("brand").toString(),
115+
QString::number(query.value("num").toInt()),
116+
query.value("create_time").toString());
117+
qDebug() << text;
118+
return true;
119+
}

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+
};

SqliteWAL/sqliteutils.cc

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#include "sqliteutils.hpp"
2+
3+
#include <QFile>
4+
#include <QSqlDriver>
5+
#include <QSqlError>
6+
#include <QSqlQuery>
7+
8+
#include <mutex>
9+
10+
void printDriverCapabilities(QSqlDriver *driver)
11+
{
12+
static std::once_flag once;
13+
std::call_once(once, [driver] {
14+
qInfo().noquote() << "SQLite driver capabilities:\n"
15+
<< " Transactions :" << driver->hasFeature(QSqlDriver::Transactions)
16+
<< '\n'
17+
<< " LastInsertId :" << driver->hasFeature(QSqlDriver::LastInsertId)
18+
<< '\n'
19+
<< " BatchOperations :"
20+
<< driver->hasFeature(QSqlDriver::BatchOperations) << '\n'
21+
<< " SimpleLocking :" << driver->hasFeature(QSqlDriver::SimpleLocking);
22+
});
23+
}
24+
25+
void queryOptions(QSqlQuery &query, const QStringList &options)
26+
{
27+
for (const auto &option : std::as_const(options)) {
28+
if (!query.exec(option)) {
29+
qWarning() << "Failed to execute " << option << ":" << query.lastError().text();
30+
}
31+
}
32+
}
33+
34+
QSqlDatabase getDatabase(const SqliteConnection &dataBaseConnection)
35+
{
36+
auto db = QSqlDatabase::database(dataBaseConnection.connectionName);
37+
if (!db.isValid()) {
38+
db = QSqlDatabase::addDatabase("QSQLITE", dataBaseConnection.connectionName);
39+
db.setDatabaseName(dataBaseConnection.dataBasePath);
40+
}
41+
42+
if (!db.isOpen()) {
43+
if (!db.open()) {
44+
QFile(dataBaseConnection.dataBasePath).remove();
45+
db.open();
46+
}
47+
static const QStringList options = {
48+
"PRAGMA journal_mode = WAL;", // 启用WAL模式
49+
"PRAGMA optimize = 0x10002;", // 启用自动索引和查询优化
50+
"PRAGMA auto_vacuum = INCREMENTAL;", // 启用自动VACUUM
51+
"PRAGMA default_temp_store = MEMORY;", // 启用内存临时表
52+
"PRAGMA temp_store = MEMORY;", // 启用内存临时表
53+
"PRAGMA page_size = 4096;", // 设置页面大小
54+
"PRAGMA cache_size = -32768;", // 设置缓存大小,单位为KB
55+
"PRAGMA wal_autocheckpoint = 1000;", // 设置WAL自动检查点间隔
56+
"PRAGMA mmap_size = 268435456;", // 启用内存映射,最大256MB
57+
"PRAGMA cache_spill = false;" // 禁用缓存溢出
58+
// "PRAGMA synchronous = NORMAL;", // 性能提升,耐久性略降
59+
// "PRAGMA locking_mode = EXCLUSIVE;", // 独占锁,适合单进程
60+
// "PRAGMA foreign_keys = ON;", // 启用外键约束
61+
};
62+
QSqlQuery query(db);
63+
queryOptions(query, options);
64+
65+
printDriverCapabilities(db.driver());
66+
}
67+
68+
return db;
69+
}
70+
71+
void removeDatabase(const SqliteConnection &dataBaseConnection)
72+
{
73+
auto connectionName = dataBaseConnection.connectionName;
74+
if (!QSqlDatabase::contains(connectionName)) {
75+
return;
76+
}
77+
static const QStringList options = {
78+
"ANALYZE;", // 分析数据库
79+
"PRAGMA optimize;", // 优化数据库
80+
"PRAGMA incremental_vacuum;", // 增量VACUUM
81+
"PRAGMA journal_size_limit = 8388608;", // 限制日志文件大小为8MB
82+
"PRAGMA wal_checkpoint(TRUNCATE);", // 检查点并截断WAL文件
83+
"PRAGMA shrink_memory;", // 释放内存
84+
};
85+
{
86+
QSqlQuery query(getDatabase(dataBaseConnection));
87+
queryOptions(query, options);
88+
}
89+
90+
QSqlDatabase::removeDatabase(connectionName);
91+
}
92+
93+
QString getDatabaseConnectionName()
94+
{
95+
static std::atomic_llong id = 1;
96+
return QString("connection_%1").arg(id.fetch_add(1));
97+
}

0 commit comments

Comments
 (0)