sqlite数据库连接池

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();
}

核心特性说明

  1. 线程安全

    • 使用 QMutex 保证连接池的读写互斥;
    • 使用 QWaitCondition 实现「无空闲连接时的阻塞等待」,避免频繁创建连接。
  2. 连接有效性

    • 获取连接时执行 SELECT 1 验证连接是否可用,失效则自动重建。
  3. SQLite 优化参数

    • QSQLITE_BUSY_TIMEOUT=5000:遇到数据库锁时等待 5 秒,避免直接报错;
    • QSQLITE_ENABLE_SHARED_CACHE:多连接共享缓存,减少内存占用。
  4. 连接名称唯一性

    • 连接名称 = 前缀 + 连接数 + 线程 ID,避免 QSqlDatabase 名称冲突。

注意事项

  1. 连接必须归还 :使用完连接后必须调用 releaseConnection,否则会导致连接池耗尽。
  2. 避免长连接占用:业务逻辑应尽快释放连接,不要长时间持有。
  3. 线程内复用连接:同一个线程多次操作数据库时,建议复用同一个连接(无需每次都获取 / 归还)。
  4. 数据库文件权限:确保程序对 SQLite 数据库文件所在目录有读写权限。
  5. 最大连接数设置:SQLite 单文件数据库的连接数不宜过大(建议 5~20),过多连接会增加锁竞争。
相关推荐
减_简5 小时前
JVM 之 线上诊断神器Arthas【常用命令?如何使用Arthas排查cpu飙高、类加载问题、死锁、慢接口等问题?】
jvm
7ioik5 小时前
jvm性能检测及调优?
jvm
何中应5 小时前
【面试题-4】JVM
java·jvm·后端·面试题
VX:Fegn08955 小时前
计算机毕业设计|基于springboot + vue非遗传承文化管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
bleach-5 小时前
buuctf系列解题思路祥讲--[极客大挑战 2019]HardSQL1——sql报错注入
数据库·sql·安全·web安全·网络安全
7ioik5 小时前
jvm垃圾回收算法?
jvm·算法
少年攻城狮5 小时前
OceanBase系列---【oracle模式的存在即更新,不存在即新增的merge into用法】
数据库·oracle·oceanbase
波波仔865 小时前
clickhouse简介
数据库·clickhouse
不穿格子的程序员6 小时前
Redis篇2——Redis深度剖析:从SetNX到Redisson,分布式锁的进化之路
数据库·redis·分布式锁·redisson·setnx·redlock