Skip to content

Commit 0fc6114

Browse files
committed
[SqliteWAL功能增强] SqliteWAL模式下多线程并发写入数据库程序更新
- 新增:`databasetest.cc` 和 `databasetest.hpp` 文件,增加了`DataBaseTest`类,用于测试数据库操作。 - 新增:`databaseutils.cc` 和 `databaseutils.hpp` 文件,提供了数据库连接和移除的工具函数。 - 修改:`main.cc` 文件,更新了多线程插入数据的方式,使用新的`DataBaseTest`类。 - 修改:`CMakeLists.txt` 和 `SqliteWAL.pro` 文件,添加了新的源文件和头文件。 - 删除:`sqlutils.cc` 和 `sqlutils.hpp` 文件,移除了旧的`SqlUtils`类。 - 更新:`README.md` 文件,对SqliteWAL模式的描述进行了更新和补充。
1 parent d5f2efa commit 0fc6114

File tree

10 files changed

+189
-158
lines changed

10 files changed

+189
-158
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,17 +150,19 @@
150150
151151
## [SqliteWAL](SqliteWAL/)——Sqlite WAL 模式下多线程并发写入数据库程序
152152
153+
1. 每个线程拥有独立的数据库连接(不同的连接名称),线程退出需要主动移除数据库连接,不然会产生大量的数据库连接;
154+
2. 在多线程下,依旧使用QMutex保证线程安全,在读的时候,可以考虑不使用QMutex,并发读应该没什么影响(对于写的时候,可以考虑使用QMutex);
155+
153156
### WAL模式的优点
154157
155-
1. 提高了并发性:WAL模式允许多个读取器和一个写入器同时访问数据库,可以提高并发性能和性能
158+
1. 提高了并发性:WAL模式允许多个读取器和一个写入器同时访问数据库,可以提高并发性能
156159
2. 崩溃恢复:WAL模式在发生崩溃时确保数据库保持一致,通过在提交事务之前将所有更改刷新到日志文件来实现;
157-
3. 改进了写入性能:WAL模式允许并发写入,可以比默认的回滚模式更好地改进写入性能;
158160
159161
### WAL模式的注意事项
160162
161163
1. WAL模式仅适用于SQLite 3.35.5+版本;
162164
2. 增加了磁盘使用量:与回滚模式相比,WAL模式需要更多的磁盘空间,因为它在提交更改之前将所有更改都写入日志文件;
163-
3. 读取性能较慢:在WAL模式下,读取操作不会被写入操作阻塞,如果同时进行读取和写入操作,可能导致数据不一致。
165+
3. 读取性能较慢:读取操作不会被写入操作阻塞,如果同时进行读取和写入操作,可能导致数据不一致。
164166
165167
## [TableViewModel](TableViewModel/)——表格视图
166168

SqliteWAL/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
set(PROJECT_SOURCES main.cc sqlutils.cc sqlutils.hpp)
1+
set(PROJECT_SOURCES databasetest.cc databasetest.hpp databaseutils.cc
2+
databaseutils.hpp main.cc)
23

34
qt_add_executable(SqliteWAL MANUAL_FINALIZATION ${PROJECT_SOURCES})
45
target_link_libraries(SqliteWAL PRIVATE Qt6::Core Qt6::Sql)

SqliteWAL/SqliteWAL.pro

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ CONFIG += c++17 cmdline
77
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
88

99
SOURCES += \
10-
main.cc \
11-
sqlutils.cc
10+
databasetest.cc \
11+
databaseutils.cc \
12+
main.cc
1213

1314
# Default rules for deployment.
1415
qnx: target.path = /tmp/$${TARGET}/bin
1516
else: unix:!android: target.path = /opt/$${TARGET}/bin
1617
!isEmpty(target.path): INSTALLS += target
1718

1819
HEADERS += \
19-
sqlutils.hpp
20+
databasetest.hpp \
21+
databaseutils.hpp

SqliteWAL/databasetest.cc

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#include "databasetest.hpp"
2+
#include "databaseutils.hpp"
3+
4+
#include <QDir>
5+
#include <QMutex>
6+
#include <QSqlDriver>
7+
#include <QSqlError>
8+
#include <QSqlQuery>
9+
#include <QThread>
10+
11+
class DataBaseTest::DataBaseTestPrivate
12+
{
13+
public:
14+
explicit DataBaseTestPrivate(DataBaseTest *q)
15+
: q_ptr(q)
16+
{
17+
dataBaseConnection.dataBasePath = QString("%1/%2").arg(QDir::tempPath()).arg("test.db");
18+
dataBaseConnection.connectionName = getDatabaseConnectionName();
19+
20+
createTable();
21+
22+
qInfo() << "DataBaseTestPrivate connectionName: " << dataBaseConnection.connectionName
23+
<< QThread::currentThread();
24+
}
25+
26+
~DataBaseTestPrivate() { removeDatabase(dataBaseConnection.connectionName); }
27+
28+
bool createTable()
29+
{
30+
const auto createTable
31+
= QString("CREATE TABLE [%1]("
32+
" [id] INTEGER NOT NULL ON CONFLICT REPLACE UNIQUE ON CONFLICT "
33+
"REPLACE COLLATE BINARY, "
34+
" [brand] TEXT, "
35+
" [num] TEXT, "
36+
" [create_time] TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "
37+
" PRIMARY KEY([id] COLLATE [BINARY] ASC) ON CONFLICT REPLACE)")
38+
.arg(tableName);
39+
40+
QMutexLocker locker(&mutex);
41+
auto db = getDatabase(dataBaseConnection);
42+
CHECK_DATABASE_VALIDITY(db)
43+
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+
50+
if (db.tables().contains(tableName)) {
51+
return true;
52+
}
53+
54+
QSqlQuery query(db);
55+
if (!query.exec(createTable)) {
56+
qWarning() << query.lastError().text();
57+
return false;
58+
}
59+
return true;
60+
}
61+
62+
DataBaseTest *q_ptr;
63+
64+
DataBaseConnection dataBaseConnection;
65+
const QString tableName = "phone";
66+
67+
static QMutex mutex;
68+
};
69+
70+
QMutex DataBaseTest::DataBaseTestPrivate::mutex;
71+
72+
DataBaseTest::DataBaseTest(QObject *parent)
73+
: QObject{parent}
74+
, d_ptr(new DataBaseTestPrivate(this))
75+
{}
76+
77+
DataBaseTest::~DataBaseTest() {}
78+
79+
bool DataBaseTest::insert(const QString &brand, int num)
80+
{
81+
QMutexLocker locker(&d_ptr->mutex);
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+
if (!query.exec()) {
90+
qCritical() << query.lastError().text();
91+
return false;
92+
}
93+
qInfo() << "Last inserted id:" << query.lastInsertId().toInt();
94+
return true;
95+
}

SqliteWAL/databasetest.hpp

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

SqliteWAL/databaseutils.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#include "databaseutils.hpp"
2+
3+
#include <QFile>
4+
#include <QSqlQuery>
5+
6+
QSqlDatabase getDatabase(const DataBaseConnection &dataBaseConnection)
7+
{
8+
auto db = QSqlDatabase::database(dataBaseConnection.connectionName);
9+
if (!db.isValid()) {
10+
db = QSqlDatabase::addDatabase("QSQLITE", dataBaseConnection.connectionName);
11+
db.setDatabaseName(dataBaseConnection.dataBasePath);
12+
}
13+
14+
if (!db.isOpen()) {
15+
if (!db.open()) {
16+
QFile(dataBaseConnection.dataBasePath).remove();
17+
db.open();
18+
}
19+
QSqlQuery walQuery(db);
20+
walQuery.exec("PRAGMA journal_mode = WAL;");
21+
}
22+
23+
return db;
24+
}
25+
26+
void removeDatabase(const QString &connectionName)
27+
{
28+
if (QSqlDatabase::contains(connectionName)) {
29+
QSqlDatabase::removeDatabase(connectionName);
30+
}
31+
}
32+
33+
QString getDatabaseConnectionName()
34+
{
35+
static std::atomic_llong id = 1;
36+
return QString("connection_%1").arg(id.fetch_add(1, std::memory_order_seq_cst));
37+
}

SqliteWAL/databaseutils.hpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#pragma once
2+
3+
#include <QSqlDatabase>
4+
5+
struct DataBaseConnection
6+
{
7+
QString connectionName;
8+
QString dataBasePath;
9+
};
10+
11+
QSqlDatabase getDatabase(const DataBaseConnection &dataBaseConnection);
12+
13+
void removeDatabase(const QString &connectionName);
14+
15+
QString getDatabaseConnectionName();
16+
17+
#define CHECK_DATABASE_VALIDITY(database) \
18+
if (!(database).isValid()) { \
19+
qWarning() << (database).lastError().text(); \
20+
return false; \
21+
}

SqliteWAL/main.cc

Lines changed: 7 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,15 @@
1-
#include "sqlutils.hpp"
1+
#include "databasetest.hpp"
22

33
#include <QCoreApplication>
4-
#include <QMutex>
54
#include <QSqlDatabase>
6-
#include <QWaitCondition>
75

86
#include <thread>
97

10-
class CountDownLatch
8+
void insertThread(const QString &brand)
119
{
12-
Q_DISABLE_COPY_MOVE(CountDownLatch)
13-
public:
14-
explicit CountDownLatch(int count)
15-
: m_count(count)
16-
{}
17-
void countDown()
18-
{
19-
QMutexLocker lock(&m_mutex);
20-
if (--m_count == 0) {
21-
m_condition.wakeAll();
22-
}
23-
}
24-
int count() const
25-
{
26-
QMutexLocker lock(&m_mutex);
27-
return m_count;
28-
}
29-
30-
void wait()
31-
{
32-
QMutexLocker lock(&m_mutex);
33-
while (m_count > 0) {
34-
m_condition.wait(&m_mutex);
35-
}
36-
}
37-
38-
private:
39-
mutable QMutex m_mutex;
40-
QWaitCondition m_condition;
41-
int m_count;
42-
};
43-
44-
void insertThread(const QString &brand, CountDownLatch &countDown)
45-
{
46-
SqlUtils sqlUtils;
47-
countDown.wait();
48-
for (int i = 0; i < 500; i++) {
49-
sqlUtils.insert(brand, i);
10+
DataBaseTest test;
11+
for (int i = 0; i < 1000; i++) {
12+
test.insert(brand, i);
5013
}
5114
}
5215

@@ -60,12 +23,10 @@ int main(int argc, char *argv[])
6023
}
6124

6225
const QStringList brands{"Apple", "Samsung", "Xiaomi", "Huawei", "Oppo", "Vivo", "Realme"};
63-
CountDownLatch countDown(1);
6426
std::vector<std::thread> threads;
65-
for (const auto &brand : brands) {
66-
threads.emplace_back(insertThread, brand, std::ref(countDown));
27+
for (const auto &brand : std::as_const(brands)) {
28+
threads.emplace_back(insertThread, brand);
6729
}
68-
countDown.countDown();
6930
for (auto &thread : threads) {
7031
if (thread.joinable()) {
7132
thread.join();

SqliteWAL/sqlutils.cc

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

0 commit comments

Comments
 (0)