Qt 实现 SQLite 连接池(线程安全版)
SQLite 本身支持多线程,但单个连接不能被多线程同时使用,因此连接池的核心是:管理一组独立的数据库连接,为每个线程分配 / 复用连接,保证线程安全,避免频繁创建 / 销毁连接的性能损耗。
以下是完整的连接池实现,包含「单例模式、线程安全、连接有效性检查、最大连接数限制」核心特性:
1. 头文件(SqliteConnectionPool.h)
cpp
运行
#ifndef SQLITECONNECTIONPOOL_H #define SQLITECONNECTIONPOOL_H #include <QSqlDatabase> #include <QStack> #include <QMutex> #include <QString> #include <QWaitCondition> // SQLite 连接池(单例模式 + 线程安全) class SqliteConnectionPool { public: // 获取单例实例(C++11 线程安全的局部静态变量) static SqliteConnectionPool& getInstance(); // 获取数据库连接(若空闲连接不足则创建新连接,超出最大数则等待) QSqlDatabase getConnection(); // 归还数据库连接到连接池 void releaseConnection(const QSqlDatabase& db); // 设置连接池配置 void setConfig(const QString& dbPath, int maxConn = 10); // 释放所有连接(析构时自动调用) void releaseAllConnections(); private: // 私有构造/析构,禁止拷贝/赋值(单例约束) SqliteConnectionPool(); ~SqliteConnectionPool(); SqliteConnectionPool(const SqliteConnectionPool&) = delete; SqliteConnectionPool& operator=(const SqliteConnectionPool&) = delete; // 检查连接是否有效(执行简单 SQL 验证) bool isConnectionValid(const QSqlDatabase& db); // 创建新的数据库连接 QSqlDatabase createNewConnection(); private: QMutex m_mutex; // 线程安全锁 QWaitCondition m_cond; // 等待条件(无空闲连接时阻塞) QStack<QString> m_freeConnNames; // 空闲连接名称栈(QSqlDatabase 按名称管理) QString m_dbPath; // SQLite 数据库文件路径 int m_maxConn = 10; // 最大连接数(默认10) int m_curConn = 0; // 当前已创建的连接数 const QString m_connPrefix = "SqliteConn_"; // 连接名称前缀(保证唯一性) }; #endif // SQLITECONNECTIONPOOL_H2. 源文件(SqliteConnectionPool.cpp)
cpp
运行
#include "SqliteConnectionPool.h" #include <QSqlQuery> #include <QDebug> #include <QThread> // 单例实例获取 SqliteConnectionPool& SqliteConnectionPool::getInstance() { static SqliteConnectionPool instance; return instance; } // 构造函数(私有) SqliteConnectionPool::SqliteConnectionPool() { // 注册 SQLite 驱动(Qt 5+ 自动注册,此处兼容低版本) qRegisterMetaType<QSqlDatabase>("QSqlDatabase"); } // 析构函数(私有) SqliteConnectionPool::~SqliteConnectionPool() { releaseAllConnections(); } // 设置连接池配置(数据库路径 + 最大连接数) void SqliteConnectionPool::setConfig(const QString& dbPath, int maxConn) { QMutexLocker locker(&m_mutex); m_dbPath = dbPath; m_maxConn = qMax(1, maxConn); // 最大连接数至少为1 } // 获取数据库连接 QSqlDatabase SqliteConnectionPool::getConnection() { QMutexLocker locker(&m_mutex); // 步骤1:优先使用空闲连接 while (m_freeConnNames.isEmpty()) { // 空闲连接为空时,判断是否可创建新连接 if (m_curConn < m_maxConn) { // 创建新连接 createNewConnection(); } else { // 超出最大连接数,等待其他线程归还连接(超时30秒) if (!m_cond.wait(&m_mutex, 30000)) { qWarning() << "获取SQLite连接超时(30秒),当前连接数已达上限:" << m_maxConn; return QSqlDatabase(); // 返回无效连接 } } } // 步骤2:取出空闲连接并验证有效性 QString connName = m_freeConnNames.pop(); QSqlDatabase db = QSqlDatabase::database(connName, false); // false:不自动打开 // 连接失效则重建 if (!isConnectionValid(db)) { qDebug() << "连接失效,重建连接:" << connName; db = createNewConnection(); } return db; } // 归还连接到池 void SqliteConnectionPool::releaseConnection(const QSqlDatabase& db) { if (!db.isValid()) return; QMutexLocker locker(&m_mutex); QString connName = db.connectionName(); // 确保连接未被重复归还 if (!m_freeConnNames.contains(connName)) { m_freeConnNames.push(connName); m_cond.wakeOne(); // 唤醒等待连接的线程 qDebug() << "归还连接:" << connName << ",当前空闲连接数:" << m_freeConnNames.size(); } } // 释放所有连接 void SqliteConnectionPool::releaseAllConnections() { QMutexLocker locker(&m_mutex); while (!m_freeConnNames.isEmpty()) { QString connName = m_freeConnNames.pop(); QSqlDatabase::removeDatabase(connName); m_curConn--; } qDebug() << "已释放所有SQLite连接,总计:" << (m_maxConn - m_curConn); } // 检查连接有效性 bool SqliteConnectionPool::isConnectionValid(const QSqlDatabase& db) { if (!db.isOpen()) return false; // 执行简单SQL验证连接(SQLite 通用) QSqlQuery query(db); return query.exec("SELECT 1"); } // 创建新连接 QSqlDatabase SqliteConnectionPool::createNewConnection() { // 生成唯一连接名称(前缀 + 当前连接数 + 线程ID,避免冲突) QString connName = m_connPrefix + QString::number(m_curConn) + "_" + QString::number((qlonglong)QThread::currentThreadId()); // 创建SQLite连接 QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", connName); db.setDatabaseName(m_dbPath); // SQLite 优化参数(可选,根据业务调整) db.setConnectOptions( "QSQLITE_OPEN_URI=1;" // 启用URI模式 "QSQLITE_ENABLE_SHARED_CACHE=1;" // 共享缓存(多连接时减少内存占用) "QSQLITE_BUSY_TIMEOUT=5000" // 忙时等待5秒(避免锁冲突) ); // 打开连接 if (db.open()) { m_curConn++; qDebug() << "创建新连接成功:" << connName << ",当前总连接数:" << m_curConn; } else { qCritical() << "创建SQLite连接失败:" << db.lastError().text(); QSqlDatabase::removeDatabase(connName); // 清理无效连接 } return db; }3. 使用示例(多线程场景)
cpp
运行
#include <QCoreApplication> #include <QThread> #include <QSqlQuery> #include <QDebug> #include "SqliteConnectionPool.h" // 工作线程:执行SQL操作 class SqlWorker : public QThread { protected: void run() override { // 1. 获取连接 QSqlDatabase db = SqliteConnectionPool::getInstance().getConnection(); if (!db.isValid() || !db.open()) { qWarning() << "线程" << QThread::currentThreadId() << "获取连接失败:" << db.lastError().text(); return; } // 2. 执行SQL(示例:创建表 + 插入数据) QSqlQuery query(db); // 创建表(仅第一次执行有效) if (!query.exec("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, name TEXT)")) { qWarning() << "创建表失败:" << query.lastError().text(); } // 插入数据 query.prepare("INSERT INTO test (name) VALUES (:name)"); query.bindValue(":name", "Thread_" + QString::number((qlonglong)QThread::currentThreadId())); if (query.exec()) { qDebug() << "线程" << QThread::currentThreadId() << "插入数据成功,ID:" << query.lastInsertId().toInt(); } else { qWarning() << "插入数据失败:" << query.lastError().text(); } // 3. 归还连接 SqliteConnectionPool::getInstance().releaseConnection(db); } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 初始化连接池 SqliteConnectionPool& pool = SqliteConnectionPool::getInstance(); pool.setConfig("./test.db", 5); // 数据库路径 + 最大5个连接 // 创建10个工作线程(测试连接池复用) QList<SqlWorker*> workers; for (int i = 0; i < 10; i++) { SqlWorker* worker = new SqlWorker; workers.append(worker); worker->start(); } // 等待所有线程结束 for (SqlWorker* worker : workers) { worker->wait(); delete worker; } return a.exec(); }核心特性说明
线程安全:
- 使用
QMutex保证连接池的读写互斥; - 使用
QWaitCondition实现「无空闲连接时的阻塞等待」,避免频繁创建连接。
- 使用
连接有效性:
- 获取连接时执行
SELECT 1验证连接是否可用,失效则自动重建。
- 获取连接时执行
SQLite 优化参数:
QSQLITE_BUSY_TIMEOUT=5000:遇到数据库锁时等待 5 秒,避免直接报错;QSQLITE_ENABLE_SHARED_CACHE:多连接共享缓存,减少内存占用。
连接名称唯一性:
- 连接名称 = 前缀 + 连接数 + 线程 ID,避免
QSqlDatabase名称冲突。
- 连接名称 = 前缀 + 连接数 + 线程 ID,避免
注意事项
- 连接必须归还:使用完连接后必须调用
releaseConnection,否则会导致连接池耗尽。 - 避免长连接占用:业务逻辑应尽快释放连接,不要长时间持有。
- 线程内复用连接:同一个线程多次操作数据库时,建议复用同一个连接(无需每次都获取 / 归还)。
- 数据库文件权限:确保程序对 SQLite 数据库文件所在目录有读写权限。
- 最大连接数设置:SQLite 单文件数据库的连接数不宜过大(建议 5~20),过多连接会增加锁竞争。