【Qt】 数据库连接池
Qt 数据库连接池
一、核心流程拆解(5步)
-
初始化连接池(
init
)- 接收数据库配置(类型、IP、端口、账号密码等)及连接池参数(最大连接数、最小空闲连接数)。
- 预先创建
minIdleConnections
个空闲连接,存入队列m_connections
,并记录总连接数m_totalConnections
。 - 标记连接池为“已初始化”(
m_isInitialized = true
)。
-
获取连接(
acquireConnection
)- 若连接池未初始化,直接返回无效连接。
- 优先从队列中取空闲连接,通过
isConnectionValid
验证有效性(执行SELECT 1
检查连接是否存活):- 有效则直接返回;
- 无效则销毁该连接(总连接数减1),继续尝试取其他连接。
- 若队列无可用连接且总连接数未达
maxConnections
,调用createConnection
创建新连接,总连接数加1并返回。 - 若连接数达上限,通过
QWaitCondition
阻塞等待(超时退出),直到有连接被释放。
-
释放连接(
releaseConnection
)- 若连接无效(未打开),直接销毁(总连接数减1)。
- 若总连接数超上限或空闲连接数已达
minIdleConnections
,销毁该连接(总连接数减1)。 - 否则将连接放回队列
m_connections
,并通过QWaitCondition::wakeOne
唤醒一个等待连接的线程。
-
关闭所有连接(
closeAllConnections
)- 遍历队列中所有空闲连接,关闭连接并通过
QSqlDatabase::removeDatabase
彻底移除。 - 重置总连接数为0,标记连接池为“未初始化”。
- 遍历队列中所有空闲连接,关闭连接并通过
-
辅助功能
createConnection
:生成唯一连接名,创建QSqlDatabase
实例并打开连接(失败则清理无效连接)。isConnectionValid
:通过执行SELECT 1
检查连接是否存活(避免使用已断开的连接)。
二、核心知识点
-
线程同步机制
QMutex
:保证连接池操作(获取/释放连接、初始化)的线程安全,避免多线程并发冲突。QWaitCondition
:当连接池无可用连接时,阻塞等待线程(wait
);当连接被释放时,唤醒等待线程(wakeOne
),实现连接的高效复用。
-
数据库连接管理
QSqlDatabase
:封装数据库连接,支持多类型数据库(MySQL、SQLite、PostgreSQL),通过唯一连接名(conn_%1_%2
)区分不同连接。QSqlQuery
:执行SELECT 1
验证连接有效性,确保返回的连接可正常使用。
-
单例模式
- 通过
getInstance
实现单例(static DbConnectionPool instance
),保证全局只有一个连接池实例,统一管理所有数据库连接。
- 通过
-
资源复用与控制
- 预先创建空闲连接,避免频繁创建/销毁连接的开销(连接创建是重量级操作,尤其对远程数据库)。
- 通过
maxConnections
限制并发连接数,防止数据库因连接过多崩溃。
三、使用示例(Qt环境)
// 1. 初始化连接池(程序启动时调用)
DbConnectionPool::getInstance().init(databaseType::QMYSQL, // 数据库类型"127.0.0.1", // IP3306, // 端口"mydb", // 数据库名"root", // 用户名"123456", // 密码10, // 最大连接数3 // 最小空闲连接数
);// 2. 获取连接并执行查询(业务逻辑中)
QSqlDatabase db = DbConnectionPool::getInstance().acquireConnection(3000); // 超时3秒
if (db.isValid() && db.isOpen()) {QSqlQuery query(db);if (query.exec("SELECT * FROM user")) {while (query.next()) {// 处理查询结果QString name = query.value("name").toString();}} else {qDebug() << "查询失败:" << query.lastError().text();}// 3. 释放连接(必须调用,否则连接池会耗尽)DbConnectionPool::getInstance().releaseConnection(db);
}// 4. 程序退出时关闭所有连接
DbConnectionPool::getInstance().closeAllConnections();
四、使用连接池的核心优点
-
提高性能
避免频繁创建/销毁数据库连接(连接创建需握手、认证等耗时操作),通过复用空闲连接减少资源开销,尤其适合高并发场景。 -
控制资源占用
通过maxConnections
限制最大并发连接数,防止因连接过多导致数据库负载过高或崩溃。 -
统一连接管理
集中处理连接的创建、验证、销毁,简化业务代码(无需手动管理连接生命周期),且便于监控连接状态(如空闲数、总连接数)。 -
增强稳定性
内置连接有效性检查(isConnectionValid
),自动剔除无效连接,避免使用已断开的连接导致业务失败。 -
线程安全
通过QMutex
和QWaitCondition
保证多线程环境下连接的安全获取与释放,支持多线程并发访问数据库。
总结
该连接池通过“预创建+复用+监控”连接,结合Qt的线程同步机制,实现了数据库连接的高效管理,适用于需要频繁操作数据库的Qt多线程程序,可显著提升性能并降低数据库压力。
// DbConnectionPool.h
#pragma once
#include "databasesdklib_global.h"
#include "struct_def.h"
class DbConnectionPool {
public:// 单例模式(全局唯一连接池)static DbConnectionPool& getInstance();// 禁止拷贝DbConnectionPool(const DbConnectionPool&) = delete;DbConnectionPool& operator=(const DbConnectionPool&) = delete;// 初始化连接池(需在使用前调用)void init(databaseType type, const QString& ip, int port, const QString& dbName,const QString& username, const QString& password,int maxConnections = 10, int minIdleConnections = 2);// 获取连接(若池为空且未达最大连接数,则创建新连接;否则等待)QSqlDatabase acquireConnection(int timeoutMs = 3000);// 归还连接(将连接放回池,需确保连接未关闭)void releaseConnection(QSqlDatabase db);// 关闭所有连接(程序退出时调用)void closeAllConnections();private:DbConnectionPool() = default;~DbConnectionPool() { closeAllConnections(); }// 创建新连接QSqlDatabase createConnection();// 检查连接是否有效bool isConnectionValid(const QSqlDatabase& db);private:QQueue<QSqlDatabase> m_connections; // 可用连接队列QMutex m_mutex; // 保护队列的线程安全QWaitCondition m_waitCondition; // 等待连接可用的条件变量// 数据库配置databaseType m_type;QString m_ip;int m_port;QString m_dbName;QString m_username;QString m_password;// 连接池参数int m_maxConnections = 10; // 最大连接数int m_minIdleConnections = 2; // 最小空闲连接数int m_totalConnections = 0; // 当前总连接数(包括已借出和空闲)bool m_isInitialized = false; // 是否已初始化
public:int m_count=0;
};
// DbConnectionPool.cpp
#include "DbConnectionPool.h"DbConnectionPool& DbConnectionPool::getInstance() {static DbConnectionPool instance;return instance;
}void DbConnectionPool::init(databaseType type, const QString& ip, int port, const QString& dbName,const QString& username, const QString& password,int maxConnections, int minIdleConnections) {QMutexLocker locker(&m_mutex);if (m_isInitialized) {qWarning() << "连接池已初始化,无需重复调用";return;}// 保存配置m_type = type;m_ip = ip;m_port = port;m_dbName = dbName;m_username = username;m_password = password;m_maxConnections = maxConnections;m_minIdleConnections = minIdleConnections;// 预先创建最小空闲连接for (int i = 0; i < m_minIdleConnections; ++i) {QSqlDatabase db = createConnection();if (db.isOpen()) {m_connections.enqueue(db);m_totalConnections++;}}m_isInitialized = true;
}QSqlDatabase DbConnectionPool::acquireConnection(int timeoutMs) {QMutexLocker locker(&m_mutex);if (!m_isInitialized) {qCritical() << "连接池未初始化,请先调用init()";return QSqlDatabase();}// 循环等待可用连接(超时退出)while (true) {// 1. 检查队列中是否有可用连接if (!m_connections.isEmpty()) {QSqlDatabase db = m_connections.dequeue();// 验证连接有效性(避免连接已断开)if (isConnectionValid(db)) {return db;}else {// 连接无效,销毁并减少总连接数m_totalConnections--;continue;}}// 2. 若未达最大连接数,创建新连接if (m_totalConnections < m_maxConnections) {QSqlDatabase db = createConnection();if (db.isOpen()) {m_totalConnections++;m_count++;return db;}else {qWarning() << "创建新连接失败,将重试";continue;}}// 3. 无可用连接且已达最大连接数,等待超时if (!m_waitCondition.wait(&m_mutex, timeoutMs)) {qWarning() << "获取连接超时(" << timeoutMs << "ms),当前总连接数:" << m_totalConnections;return QSqlDatabase();}}
}void DbConnectionPool::releaseConnection(QSqlDatabase db) {if (!db.isValid()) {qWarning() << "归还无效连接,忽略";return;}QMutexLocker locker(&m_mutex);// 若连接已关闭,直接销毁if (!db.isOpen()) {m_totalConnections--;m_count--;return;}// 检查是否需要销毁(若总连接数超过最大,且空闲连接数超过最小)if (m_totalConnections > m_maxConnections || m_connections.size() >= m_minIdleConnections) {m_totalConnections--;m_count--;return;}// 否则放回队列m_connections.enqueue(db);m_waitCondition.wakeOne(); // 唤醒一个等待连接的线程
}void DbConnectionPool::closeAllConnections() {QMutexLocker locker(&m_mutex);while (!m_connections.isEmpty()) {QSqlDatabase db = m_connections.dequeue();if (db.isOpen()) {db.close();}QString connName = db.connectionName();QSqlDatabase::removeDatabase(connName); // 彻底移除连接}m_totalConnections = 0;m_isInitialized = false;
}QSqlDatabase DbConnectionPool::createConnection() {// 生成唯一连接名(避免冲突)QString connName = QString("conn_%1_%2").arg(int(QThread::currentThreadId())).arg(qrand());// 根据数据库类型创建连接QString driver;if (m_type == databaseType::QMYSQL) {driver = "QMYSQL";}else if (m_type == databaseType::QSQLITE) {driver = "QSQLITE";}else if (m_type == databaseType::QPSQL) {driver = "QPSQL";}else {qCritical() << "不支持的数据库类型";return QSqlDatabase();}QSqlDatabase db = QSqlDatabase::addDatabase(driver, connName);db.setHostName(m_ip);db.setPort(m_port);db.setDatabaseName(m_dbName);db.setUserName(m_username);db.setPassword(m_password);// 打开连接if (!db.open()) {qWarning() << "创建连接失败:" << db.lastError().text() << "(连接名:" << connName << ")";QSqlDatabase::removeDatabase(connName); // 清理失败的连接return QSqlDatabase();}return db;
}bool DbConnectionPool::isConnectionValid(const QSqlDatabase& db) {if (!db.isOpen()) {return false;}// 执行简单查询验证连接(如MySQL可用"SELECT 1",SQLite可用"SELECT 1")QSqlQuery query(db);return query.exec("SELECT 1");
}