MySQL同步连接池的学习(四)

发布于:2025-08-14 ⋅ 阅读:(18) ⋅ 点赞:(0)

一、MySQL连接池

1.1、什么是连接池

复用一定数量的池式结构。

1.2、解决了什么问题?

在这里插入图片描述

从这张图可以看到,一个连接的开销非常大,会经历连接建立、认证、权限校验等。如果频繁地创建和关闭数据库连接,将会极大地影响性能。

所以需要连接池复用连接和内存资源,提升并发处理SQL的能力。

1.3、如何实现?

  • 同步连接池:

    • 使用MySQL给的驱动或相关API实现。(libmysqlclient—纯C语言实现,libmysqlcppconn—C++封装,加入了异常处理机制)
    • 初始化内存资源,在服务器启动时使用
  • 异步连接池:

    • 需要实现MySQL的协议。
    • 处理业务

二、同步连接池实现方案

2.1、使用官方驱动

下载官方给的驱动或者库

官方链接

2.2、连接池管理

使用一个队列管理连接

std::queue<std::shared_ptr<sql::Connection>> m_freeConnPool;    // 使用智能指针进行自动化的内存管理

2.3、线程安全

模拟并发场景,智能指针中的引用计数是线程安全的,但是智能指针所指对象的线程安全问题需要开发者自行保证,所以需要通过互斥锁和条件变量类似机制来保证线程安全。

2.4、资源管理

当一个连接不再使用时,需要归还到池中,而不是直接关闭,这样可以复用连接。

基于以上几点,那么可以得到这样的类

class ConnectPool
{
    public:
        static ConnectPool* getInstance(){
            static ConnectPool m_instance;

            return &m_instance;
        }

        void initialize(const mysqlinfo &info);

        std::shared_ptr<sql::Connection> getConnection();

        void returnConnection(std::shared_ptr<sql::Connection> conn);

        void printInfo()const;

    private:
        ConnectPool()
        {
            m_driver = nullptr;
            m_isInit = false;
        }
        ~ConnectPool(){
            while(!m_freeConnPool.empty()){
                auto conn = m_freeConnPool.front();
                m_freeConnPool.pop();
                if(!conn->isClosed()){
                    conn->close();
                }
            }
        }

        ConnectPool(const ConnectPool&) = delete;
        ConnectPool& operator=(const ConnectPool&) = delete;
        ConnectPool(ConnectPool&&) = delete;

        std::shared_ptr<sql::Connection> createConnection();

        sql::Driver* m_driver;
        std::queue<std::shared_ptr<sql::Connection>> m_freeConnPool;
        mutable std::mutex m_mtx;
        std::condition_variable m_condition;

        mysqlinfo m_sqlInfo;
        bool m_isInit;
};

根据单例模式的思想,只有一个连接池实例,并提供初始化、获取连接和归还连接的接口。

那是怎么归还连接呢?

/**
* @brief 创建数据库连接
*
* 使用提供的数据库驱动和连接信息创建数据库连接,并将其加入空闲连接池。
*
* @return 成功时返回连接对象的共享指针,失败时返回nullptr。
*/
std::shared_ptr<sql::Connection> ConnectPool::createConnection()
{
    try{
        // 使用lambda表达式自定义删除器,确保连接在不再使用时被归还到池中
        auto conn = std::shared_ptr<sql::Connection>(
            m_driver->connect(m_sqlInfo.host, m_sqlInfo.user, m_sqlInfo.passwd),
            [this](sql::Connection* conn){
                returnConnection(std::shared_ptr<sql::Connection>(conn));
            }
        );

        conn->setSchema(m_sqlInfo.dbname);
        m_freeConnPool.push(conn);
        return conn;
    }catch(sql::SQLException &e){
        std::cerr<<"Failed to create connection: "<<e.what()<<std::endl;
        return nullptr;
    }
}
/**
* @brief 归还数据库连接到连接池
*
* 将使用完毕的数据库连接对象安全地返回到连接池中。函数会检查连接有效性,
* 若连接已关闭则会创建新连接替代,保证连接池的健康状态。
*
* @param conn 指向sql::Connection对象的共享指针,表示要归还的数据库连接
*
* @note 使用互斥锁保证线程安全,归还有效连接时会唤醒等待连接的线程
*/
void ConnectPool::returnConnection(std::shared_ptr<sql::Connection> conn)
{
    std::lock_guard<std::mutex> lock(m_mtx);

    // 检查连接是否有效
    if(conn->isValid() && !conn->isClosed()){
        m_freeConnPool.push(conn);
        m_condition.notify_one();
    }else{
        // 如果连接已关闭,创建新的连接代替
        std::cout<<"Connection is closed, creating a new one...\n";
        createConnection();
    }
}

那从连接池中获取一个连接对象,连接池为空怎么办?

/**
* @brief 获取数据库连接
*
* 获取一个指向 sql::Connection 对象的 shared_ptr 智能指针。
*
* @return std::shared_ptr<sql::Connection> 指向 sql::Connection 对象的智能指针
*/
std::shared_ptr<sql::Connection> ConnectPool::getConnection()
{
    std::unique_lock<std::mutex> lock(m_mtx);
    // 等待直到有可用连接
    if(m_freeConnPool.empty()){
        m_condition.wait(lock, [this](){ return !m_freeConnPool.empty(); });
    }
    auto conn = m_freeConnPool.front();
    m_freeConnPool.pop();

    // 检查链接是否有效
    if(!conn->isValid() || conn->isClosed()){
        try{
            conn->reconnect();
            std::cout<<"Reconnecting..."<<std::endl;
        }catch(sql::SQLException &e){
            std::cerr<<"Failed to reconnect: "<<e.what()<<std::endl;
            conn = createConnection();
        }
    }
    return conn;
}

2.5、测试场景

创建10个线程并发访问数据库,连接池的容量设置为5,测试连接池是否正常工作。

int main()
{
    auto pool = ConnectPool::getInstance();
    mysqlinfo info("tcp://127.0.0.1:3306", "root", "123456", "sql", Port, Mysql_Max_Connect);
    pool->initialize(info);

    pool->printInfo();

    // 创建多个线程,每个线程都从连接池中获取一个连接并执行一些操作
    std::vector<std::thread> threads;
    for(int i = 0; i < Max_Thread; ++i){
        threads.emplace_back(threadWort, i);
    }

    for(auto &t : threads){
        t.join();
    }
    pool->printInfo();
    return 0;
}

在这里插入图片描述

可以看见,能够正常地从连接池中获取到连接,并且在归还时能够正确地处理无效的连接,实现连接对象的复用

三、拓展

官方给出的相关API,无明显看到与服务器建立套接字连接的API;实际上是被封装在底层驱动了;官方为了支持多系统平台,所以采用的是select网络IO模型,这也是select的优点,但缺点也很明显,在高并发场景下性能瓶颈很明显。

Code


网站公告

今日签到

点亮在社区的每一天
去签到