数据库连接池(最新版)

基础知识

什么是数据库连接池?

池是一组资源的集合,这组资源在服务器启动之初就被完全创建好并初始化。通俗来说,池是资源的容器,本质上是对资源的复用。

顾名思义,连接池中的资源为一组数据库连接,由程序动态地对池中的连接进行使用,释放。

当系统开始处理客户请求的时候,如果它需要相关的资源,可以直接从池中获取,无需动态分配;当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调用释放资源。

数据库访问的一般流程是什么?

当系统需要访问数据库时,先系统创建数据库连接,完成数据库操作,然后系统断开数据库连接。

为什么要创建连接池?

从一般流程中可以看出,若系统需要频繁访问数据库,则需要频繁创建和断开数据库连接,而创建数据库连接是一个很耗时的操作,也容易对数据库造成安全隐患。

在程序初始化的时候,集中创建多个数据库连接,并把他们集中管理,供程序使用,可以保证较快的数据库读写速度,更加安全可靠。

整体概述

池可以看做资源的容器,所以多种实现方法,比如数组、链表、队列等。这里,使用单例模式和链表创建数据库连接池,实现对数据库连接资源的复用。

项目中的数据库模块分为两部分,其一是数据库连接池的定义,其二是利用连接池完成登录和注册的校验功能。具体的,工作线程从数据库连接池取得一个连接,访问数据库中的数据,访问完毕后将连接交还连接池。

本文内容

本篇将介绍数据库连接池的定义,具体的涉及到单例模式创建、连接池代码实现、RAII机制释放数据库连接。

单例模式创建,结合代码描述连接池的单例实现。

连接池代码实现,结合代码对连接池的外部访问接口进行详解。

RAII机制释放数据库连接,描述连接释放的封装逻辑。

单例模式创建

使用局部静态变量懒汉模式创建连接池。

/**
 * @brief 数据库连接池类
 * 
 * 该类用于管理数据库连接,提供获取和释放数据库连接的方法。通过连接池可以高效地重用数据库连接,
 * 提高系统性能。实现了单例模式,确保整个系统中只有一个连接池实例。
 */
class connection_pool {
public:
    /**
     * @brief 获取一个空闲的数据库连接
     * 
     * 从连接池中取出一个空闲的数据库连接供使用。
     * 
     * @return MYSQL* 数据库连接指针
     */
    MYSQL *GetConnection();

    /**
     * @brief 释放使用的数据库连接
     * 
     * 将使用完的数据库连接放回连接池中,以便后续使用。
     * 
     * @param conn 要释放的数据库连接指针
     * @return true 释放成功
     * @return false 释放失败
     */
    bool ReleaseConnection(MYSQL *conn);

    /**
     * @brief 获取当前连接池中空闲连接的数量
     * 
     * @return int 空闲连接数量
     */
    int GetFreeConn();

    /**
     * @brief 销毁连接池
     * 
     * 关闭并释放连接池中所有数据库连接,同时销毁连接池对象。
     */
    void DestroyPool();

    /**
     * @brief 单例模式获取连接池实例
     * 
     * 确保系统中只有一个连接池实例,提供一个全局访问点。
     * 
     * @return connection_pool* 连接池实例指针
     */
    static connection_pool* GetInstance();

    /**
     * @brief 初始化连接池
     * 
     * 对连接池进行初始化设置,包括数据库服务器地址、用户信息、数据库名、端口、最大连接数等。
     * 
     * @param url 数据库服务器地址
     * @param User 用户名
     * @param PassWord 密码
     * @param DataBaseName 数据库名
     * @param Port 端口号
     * @param MaxConn 最大连接数
     * @param close_log 关闭日志的标志
     */
    void init(string url, string User, string PassWord, string DataBaseName, int Port, int MaxConn, int close_log);

private:
    /**
     * @brief 构造函数私有化,防止外部创建连接池实例
     */
    connection_pool();

    /**
     * @brief 析构函数,释放连接池中所有连接
     */
    ~connection_pool();

    // 连接池相关属性和成员变量
    int m_MaxConn;       // 最大连接数
    int m_CurConn;       // 当前已使用的连接数
    int m_FreeConn;      // 当前空闲的连接数
    locker lock;         // 锁,用于同步访问连接池
    list<MYSQL*> connList; // 连接列表
    sem reserve;         // 信号量,用于同步控制连接的获取和释放

public:
    // 以下为数据库连接相关信息,公开成员变量方便访问,实际应用中建议封装
    string m_url;
    string m_Port;
    string m_User;
    string m_PassWord;
    string m_DatabaseName;
    int m_close_log;
};

连接池代码实现

连接池的定义中注释比较详细,这里仅对其实现进行解析。

连接池的功能主要有:初始化,获取连接、释放连接,销毁连接池。

初始化

值得注意的是,销毁连接池没有直接被外部调用,而是通过RAII机制来完成自动释放;使用信号量实现多线程争夺连接的同步机制,这里将信号量初始化为数据库的连接总数。

// 构造函数
connection_pool::connection_pool() {
    // 初始化当前连接数和空闲连接数为0
    m_CurConn = 0;
    m_FreeConn = 0;
}

// 单例模式获取connection_pool的实例
connection_pool *connection_pool::GetInstance() {
    // 使用静态局部变量确保connPool只初始化一次
    static connection_pool connPool;
    // 返回connection_pool实例的地址
    return &connPool;
}

// 初始化连接池
void connection_pool::init(string url, string User, string PassWord, string DataBaseName, int Port, int MaxConn, int close_log ) {
    // 设置数据库连接参数
    m_url = url;
    m_Port = Port;
    m_User = User;
    m_PassWord = PassWord;
    m_DatabaseName = DataBaseName;
    m_close_log = close_log;

    // 创建并初始化数据库连接
    for (int i = 0; i < MaxConn; i++) {
        MYSQL* con = nullptr;
        // 初始化MySQL连接
        con = mysql_init(con);
        if (con == nullptr) {
            // 初始化失败,记录错误并退出
            LOG_ERROR("MySQL Error");
            exit(1);
        }
        // 尝试建立与数据库的连接
        con = mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DataBaseName.c_str(), Port, nullptr, 0);
        if (con == nullptr) {
            // 连接失败,记录错误并退出
            LOG_ERROR("MySQL Error");
            exit(1);
        }
        // 将连接添加到连接列表中
        connList.push_back(con);
        // 增加空闲连接数
        ++m_FreeConn;
    }
    // 初始化信号量,用于控制连接的使用
    reserve = sem(m_FreeConn);

    // 设置最大连接数
    m_MaxConn = m_FreeConn;
}
获取、释放连接

当线程数量大于数据库连接数量时,使用信号量进行同步,每次取出连接,信号量原子减1,释放连接原子加1,若连接池内没有连接了,则阻塞等待。

另外,由于多线程操作连接池,会造成竞争,这里使用互斥锁完成同步,具体的同步机制均使用lock.h中封装好的类。

//当有请求时,从数据库连接池中返回一个可用连接,更新使用和空闲连接数
MYSQL* connection_pool::GetConnection() {
    MYSQL* con = nullptr;

    // 如果连接池中没有可用连接,直接返回空指针
    if (0 == connList.size()) {
        return nullptr;
    }
    // 等待有可用连接
    reserve.wait();

    // 加锁以保护共享资源
    lock.lock();

    // 获取连接池的第一个连接
    con = connList.front();
    // 从连接池中移除该连接
    connList.pop_front();
    // 更新可用连接数量
    --m_FreeConn;
    // 更新当前连接数量
    ++m_CurConn;
    // 解锁以释放共享资源
    lock.unlock();
    // 返回获取的连接
    return con;
}

//释放当前使用的连接
/**
 * 释放数据库连接
 * 
 * 该函数将一个数据库连接返回到连接池中。它首先检查连接是否为nullptr,
 * 如果不为nullptr,则将连接添加到连接列表中,并更新连接池的状态:增加空闲连接数,
 * 减少当前连接数。然后,通过发布信号量来通知等待的线程有可用的连接。
 * 
 * @param con 要释放的数据库连接指针
 * @return 返回true,表示连接已成功释放;如果con为nullptr,则返回false
 */
bool connection_pool::ReleaseConnection(MYSQL *con) {
    // 检查连接是否为nullptr
    if (nullptr == con) {
        return false;
    }

    // 加锁以保护连接列表和连接池状态
    lock.lock();

    // 将连接添加到连接列表中
    connList.push_back(con);
    // 更新空闲连接数
    ++m_FreeConn;
    // 更新当前连接数
    --m_CurConn;

    // 发布信号量以通知有可用的连接
    reserve.post();
    return true;
}
销毁连接池

通过迭代器遍历连接池链表,关闭对应数据库连接,清空链表并重置空闲连接和现有连接数量。

//销毁数据库连接池
void connection_pool::DestroyPool() {
    lock.lock();
    if (connList.size() > 0) {
        list<MYSQL*>::iterator it;
        for (it = connList.begin(); it != connList.end(); ++it) {
            MYSQL* con = *it;
            mysql_close(con);
        }
        m_CurConn = 0;
        m_FreeConn = 0;
        connList.clear();
    }

    lock.unlock();
}

RAII机制释放数据库连接

将数据库连接的获取与释放通过RAII机制封装,避免手动释放。

定义

这里需要注意的是,在获取连接时,通过有参构造对传入的参数进行修改。其中数据库连接本身是指针类型,所以参数需要通过双指针才能对其进行修改。

/**
 * @class connectionRAII
 * @brief RAII机制管理数据库连接的类
 * 
 * 该类用于通过Resource Acquisition Is Initialization (RAII) 机制管理数据库连接,
 * 确保在构造对象时从连接池获取一个数据库连接,而在对象析构时释放该连接回连接池。
 * 这种机制可以防止由于程序异常退出等原因导致的资源泄露。
 */
class connectionRAII {
public:
    /**
     * @brief 构造函数
     * 
     * 在构造时从连接池中获取一个数据库连接。
     * 
     * @param con 指向MYSQL对象的指针,用于存储从连接池获取的数据库连接。
     * @param connPool 指向connection_pool对象的指针,表示数据库连接池。
     */
    connectionRAII(MYSQL **con, connection_pool *connPool);

    /**
     * @brief 析构函数
     * 
     * 在对象生命周期结束时调用,确保数据库连接被正确地返回到连接池中。
     */
    ~connectionRAII();

private:
    MYSQL *conRAII; /**< 指向当前管理的数据库连接的指针 */
    connection_pool *pollRAII; /**< 指向数据库连接池的指针,用于归还连接 */
};
实现

不直接调用获取和释放连接的接口,将其封装起来,通过RAII机制进行获取和释放。

// 构造函数:初始化连接RAII对象
connectionRAII::connectionRAII(MYSQL **SQL, connection_pool* connPool) {
    // 从连接池中获取一个数据库连接指针
    *SQL = connPool->GetConnection();
    
    // 将获取的数据库连接指针赋值给成员变量
    conRAII = *SQL;
    // 将连接池指针赋值给成员变量,用于后续释放连接
    pollRAII = connPool;
}

// 析构函数:释放数据库连接回到连接池
connectionRAII::~connectionRAII() {
    // 将成员变量中的数据库连接指针释放回连接池
    pollRAII->ReleaseConnection(conRAII);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值