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_H
2. 源文件(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),过多连接会增加锁竞争。