MySQL连接池的实现

小捏哩 2026-03-16 14:04:32

MySQL连接池的实现

目录

  • MySQL连接池的实现
  • 1.什么是数据库连接池?
  • 2.数据库连接池解决了什么问题?
  • 3.连接池如何工作?
  • 4.同步连接与异步连接的区别
  • 4.1 同步连接
  • 4.2 异步连接
  • 5.同步连接池与异步连接池的区别
  • 5.1同步连接池
  • 5.2异步连接池
  • 6.单条 MySQL 连接的交互过程
  • 7.MySQL C/C++ 驱动
  • 8.异步连接池的设计与实现
  • 8.1BlockingQueue(阻塞队列)
  • 8.2 SQLOperation(SQL 操作)
  • 8.3MySQLConn(数据库连接)
  • 8.4 MySQLWorker(工作线程)
  • 8.5 QueryCallback(回调封装)
  • 8.6MySQLConnPool(连接池)
  • 9.关键代码解析
  • 9.1MySQLConnPool.h
  • 9.2MySQLConn.h
  • 9.3MySQLWorker.h
  • 9.4QueryCallback.h
  • 9.5SQLOperation.h
  • 9.6BlockingQueue.h
  • 10.使用示例
  • 11.总结

在开发高并发的数据库应用时,数据库连接的管理往往成为性能瓶颈。每次与数据库交互都经历 TCP 连接建立、认证、SQL 执行和连接释放的过程,开销极大。连接池技术通过复用一组预先建立的连接,显著降低了连接开销,提升了系统吞吐量。本文将深入探讨数据库连接池的原理,并给出一个基于 C++ 的异步 MySQL 连接池的实现。


1.什么是数据库连接池?

数据库连接池是一种维护若干数据库连接的技术,应用程序需要操作数据库时,从池中获取一个空闲连接,使用完毕后归还给池,而不是关闭连接。连接池通常负责连接的创建、分配、管理和释放。

2.数据库连接池解决了什么问题?

  1. 减少连接建立和销毁的开销
    每一次数据库连接都涉及 TCP 三次握手、MySQL 认证、可能的 SSL 握手等,这些操作非常耗时。连接池复用连接,消除了频繁建立和断开连接的开销。
  2. 提高资源利用率
    数据库服务器能同时维护的连接数有限。连接池可以限制最大连接数,避免应用创建过多连接压垮数据库,同时通过队列机制让请求等待空闲连接,实现资源的平滑复用。
  3. 提升响应速度
    由于连接已预先建立,请求到达时可以直接执行 SQL,减少了等待时间。
  4. 简化连接管理
    连接池统一管理连接的生命周期,包括自动重连、连接有效性检测、空闲回收等,开发者无需在代码中分散处理这些细节。

3.连接池如何工作?

基本工作流程如下:

  • 1.初始化时创建一定数量的数据库连接,放入池中。
  • 2.应用程序请求连接时,连接池分配一个空闲连接;若无空闲连接且池未满,则创建新连接;若池已满,则请求阻塞或返回错误。
  • 3.使用完毕后,应用程序将连接归还给池,而非关闭。
  • 4.连接池会定期检查连接的有效性,清理无效或空闲过久的连接。

4.同步连接与异步连接的区别

4.1 同步连接

同步连接是指发起数据库查询后,当前线程会阻塞直到查询完成并返回结果。代码通常这样写:

ResultSet* rs = conn->query("SELECT ...");
// 阻塞直到结果返回
processResult(rs);
  • 优点:编程模型简单直观,符合顺序思维。
  • 缺点:线程在等待 I/O 期间无法处理其他任务,对于高并发场景,需要创建大量线程,导致上下文切换开销巨大。

4.2 异步连接

异步连接发起查询后立即返回,不等待结果;当结果准备就绪时,通过回调函数、Future/Promise 或事件通知的方式通知调用方。

conn->asyncQuery("SELECT ...", [](ResultSet* rs) {
    processResult(rs);
});
// 立即返回,线程可以继续处理其他任务
  • 优点:线程无需阻塞,可以在等待期间处理其他请求,极大提高并发能力,减少线程数。
  • 缺点:编程复杂度增加,需要处理回调或状态机。

5.同步连接池与异步连接池的区别

5.1同步连接池

同步连接池通常与多线程配合:每个请求分配一个线程,从池中获取同步连接,执行查询,阻塞等待,然后返回。这种方式虽然复用了连接,但线程仍然阻塞在 I/O 上,通常使用在服务端启动时,用于初始化资源

请添加图片描述

5.2异步连接池

异步连接池则利用异步驱动(如 MySQL 的异步接口)或工作线程池:应用线程提交查询任务后立即返回,任务被放入队列,由专门的工作线程(通常较少)执行实际的同步查询,然后通过回调或 Future 将结果传回。这样应用线程不再阻塞,实现了真正的异步非阻塞。

请添加图片描述

我们实现的正是这种异步连接池。


6.单条 MySQL 连接的交互过程

  1. TCP 三次握手:与 MySQL 服务器建立 TCP 连接。
  2. MySQL 认证:发送用户名、密码等信息,完成身份验证。
  3. SQL 执行:发送 SQL 语句,接收结果集。
  4. TCP 四次挥手:断开 TCP 连接(如果连接未持久化)。

一个连接池正是通过复用步骤1-2建立的长连接,避免重复握手和认证开销。

请添加图片描述


7.MySQL C/C++ 驱动

实现连接池需要与 MySQL 交互的底层库。常用的有:

  • libmysqlclient:纯 C 实现的官方客户端库,性能高,但需要手动管理内存和错误处理。
  • **MySQL Connector/C++**(libmysqlcppconn):C++ 封装,提供面向对象接口,使用异常机制,更易于集成到 C++ 项目中。

8.异步连接池的设计与实现

我们的目标是实现一个高效的异步 MySQL 连接池,核心思想是:

  • 维护一个固定大小的连接池,每个连接对应一个工作线程。
  • 应用程序通过 Query 方法提交 SQL 语句和一个回调函数。
  • 连接池将任务封装为 SQLOperation,放入阻塞队列。
  • 工作线程从队列中取出任务,使用其绑定的连接执行 SQL,得到 ResultSet
  • 通过 std::promisestd::future 将结果传递给调用方,触发回调。

下面逐一介绍各个类的作用与实现要点。

8.1BlockingQueue(阻塞队列)

阻塞队列是任务传递的核心。它内部使用 std::queue 存储任务,利用互斥锁和条件变量实现线程安全的入队和出队。当队列为空时,Pop 操作会阻塞直到有任务加入或队列被设置为非阻塞模式。

关键设计:

  • non_block_ 标志支持优雅关闭:当连接池销毁时,设置该标志并唤醒所有等待线程,让它们退出。
  • Push 操作通知一个等待线程,避免“惊群效应”。

8.2 SQLOperation(SQL 操作)

每个 SQLOperation 封装一条 SQL 语句和一个 std::promise<std::unique_ptr<sql::ResultSet>>。工作线程执行 SQL 后,通过 promise_.set_value() 将结果传递给 Future。

8.3MySQLConn(数据库连接)

MySQLConn 代表一个物理的数据库连接,它包含:

  • 连接信息(主机、用户名、密码、数据库名)。
  • sql::Driversql::Connection 对象。
  • 一个 MySQLWorker 工作线程。
  • 阻塞队列的引用(用于工作线程取任务)。

Open() 方法建立真实连接,Query() 方法执行同步查询(由工作线程调用)。

8.4 MySQLWorker(工作线程)

每个 MySQLConn 拥有一个专属的 MySQLWorker,后者在独立线程中运行 Worker() 函数。该函数不断从阻塞队列中取出 SQLOperation,调用 conn_->Query(op->sql_) 执行,然后将结果设置到 op 的 Promise 中。

这种设计将 I/O 操作(SQL 执行)放在工作线程中,应用线程可以继续处理其他逻辑,实现了异步。

8.5 QueryCallback(回调封装)

QueryCallback 用于管理回调的调用时机。它持有一个 std::future 和用户回调函数。InvokeIfReady() 方法检查 Future 是否已就绪(即结果已返回),若就绪则获取结果并调用回调。

这个类可以用于事件循环中定期检查哪些查询已完成,从而调用回调。在简单的示例中,我们可能直接在主线程中等待 Future,但这里的设计更符合异步风格:将 Future 与回调绑定,由连接池或外部循环统一触发。

8.6MySQLConnPool(连接池)

连接池是单例(按数据库名区分),负责初始化一组连接,提供统一的查询接口 Query()

  • InitPool() 创建指定数量的 MySQLConn,每个连接启动其工作线程。
  • Query() 接受 SQL 和回调,创建一个 SQLOperation,将其 future 与回调封装为 QueryCallback 返回给调用方(或者放入某个待处理列表)。同时将操作 Push 到阻塞队列中。
  • 析构时设置队列为非阻塞模式,并等待所有工作线程结束。

9.关键代码解析

9.1MySQLConnPool.h

说明

  • instances_ 静态成员管理不同数据库的单例实例。
  • Query 返回 QueryCallback 对象,便于上层在合适的时机调用回调。
#pragma once 

#include <vector>
#include <string>
#include <functional>
#include <unordered_map>

#include <memory>
#include <cppconn/resultset.h>

#include "QueryCallback.h"

// 前置声明
template <typename T>
class BlockingQueue;

class MySQLConn; // 连接
class SQLOperation; // 操作

class MySQLConnPool {
public:
    static MySQLConnPool *GetInstance(const std::string &db); // 获取单例

    void InitPool(const std::string &url, size_t pool_size); // 初始化连接池
    QueryCallback Query(const std::string &sql, std::function<void(std::unique_ptr<sql::ResultSet>)> &&cb);

private:
    MySQLConnPool(const std::string &db) : database_(db) {} 
    ~MySQLConnPool();

    std::string database_;
    std::vector<MySQLConn *> pool_;
    static std::unordered_map<std::string, MySQLConnPool *> instances_; // 声明
    BlockingQueue<SQLOperation *> *task_queue_;
};

9.2MySQLConn.h

说明

  • MySQLConnInfo 负责解析连接字符串(格式如 tcp://127.0.0.1:3306;user=root;password=123)。
  • Open() 中初始化 driver_ 并建立连接。
  • Query() 由工作线程调用,执行同步查询并返回原始指针(所有权转移给调用者,通常包装为 unique_ptr)。
#pragma once 

#include <cppconn/driver.h>
#include <cppconn/connection.h>

#include <string>

// 前置声明
namespace sql {
    class Driver;
    class Connection;
    class SQLException;
    class Statement;
    class ResultSet;
}

template <typename T>
class BlockingQueue;

class SQLOperation; // 操作

class MySQLWorker; // 工作线程

struct MySQLConnInfo {
    explicit MySQLConnInfo(const std::string &info, const std::string &db);
    std::string user;
    std::string password;
    std::string databse;
    std::string url;
};

class MySQLConn {
public:
    MySQLConn(const std::string &info, const std::string &db, BlockingQueue<SQLOperation*> &task_queue);
    ~MySQLConn();

    int Open(); // 建立与MySQL数据库的连接
    void Close();

    sql::ResultSet *Query(const std::string &sql);

private:
    void HandlerException(sql::SQLException &e);
    sql::Driver *driver_;
    sql::Connection *conn_;
    sql::Statement *stmt_;
    MySQLWorker *worker_;
    MySQLConnInfo info_;
};

9.3MySQLWorker.h

说明

  • Start() 创建线程并运行 Worker
  • Worker 循环调用 task_queue_.Pop(op),若取到任务则执行 op->Execute(conn_),然后 delete op
  • Stop() 设置队列为非阻塞模式并等待线程结束。
#pragma once

#include <thread>


// 前置声明
template <typename T>
class BlockingQueue;

class MySQLConn; // 连接
class SQLOperation; // 操作

class MySQLWorker {
public:
    MySQLWorker(MySQLConn *conn, BlockingQueue<SQLOperation*> &task_queue);

    ~MySQLWorker();

    void Start();
    void Stop();
private:
    void Worker(); // 线程入口函数
    MySQLConn *conn_; // 连接
    std::thread worker_;
    BlockingQueue<SQLOperation*> &task_queue_; // 队列
};

9.4QueryCallback.h

说明

  • InvokeIfReady 是非阻塞检查,可用于事件循环中定期触发回调。
  • 注意 sql::ResultSet 必须为完整类型,因为 unique_ptr 需要知道删除器。
#pragma once

#include <chrono>
#include <functional>
#include <future>
#include <memory>

#include <cppconn/resultset.h>

class QueryCallback {
public:
    QueryCallback(std::future<std::unique_ptr<sql::ResultSet>> &&future,
                  std::function<void(std::unique_ptr<sql::ResultSet>)> &&cb)
        : future_(std::move(future)), cb_(std::move(cb)) {}
    
    bool InvokeIfReady() {
        if (future_.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
            auto result = future_.get();
            cb_(std::move(result));
            return true;
        }
        return false;
    }
private:
    std::future<std::unique_ptr<sql::ResultSet>> future_;
    std::function<void(std::unique_ptr<sql::ResultSet>)> cb_;
};

9.5SQLOperation.h

说明

  • Execute 中执行查询,并将结果或异常设置到 promise。
  • 使用 std::unique_ptr 自动管理 ResultSet 生命周期。
#pragma once 

#include <string>
#include <future>
#include <memory>

// NOTE: sql::ResultSet 必须完整才能进行 std::unique_ptr 的默认删除。
#include <cppconn/resultset.h>

class MySQLConn; // 连接

class SQLOperation {
public:
    explicit SQLOperation(const std::string &sql) : sql_(sql) {}

    void Execute(MySQLConn *conn); // 用于执行SQL操作并设置结果

    std::future<std::unique_ptr<sql::ResultSet>> GetFuture() {
        return promise_.get_future();
    }

private:
    std::string sql_;
    std::promise<std::unique_ptr<sql::ResultSet>> promise_;
};

9.6BlockingQueue.h

说明

  • Pop 在队列为空且非阻塞模式未开启时阻塞。
  • Cancel 用于通知所有等待线程退出。
#pragma once

#include <queue>
#include <mutex>
#include <condition_variable>

template <typename T>
class BlockingQueue {
public:
    explicit BlockingQueue(bool non_block = false) : non_block_(non_block) {}

    void Push(const T &value) {
        std::lock_guard<std::mutex> lock(mutex_);
        queue_.push(value);
        not_empty_.notify_one();
    }

    bool Pop(T &value) {
        std::unique_lock<std::mutex> lock(mutex_);

        not_empty_.wait(lock, [this] { return !queue_.empty() || non_block_;});
        if(queue_.empty()) return false;
        value = queue_.front();
        queue_.pop();
        return true;
    }

    void Cancel() {
        std::lock_guard<std::mutex> lock(mutex_);
        non_block_ = true;
        not_empty_.notify_all();
    }

private:
    bool non_block_; // 是否为非阻塞模式 默认为阻塞模式
    std::queue<T> queue_;
    std::mutex mutex_;
    std::condition_variable not_empty_; // 非空条件变量
};

10.使用示例

#include "MySQLConnPool.h"
#include "AsyncProcessor.h"

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <cppconn/resultset.h>

// g++ -o main main.cpp AsyncProcessor.cpp MySQLConn.cpp MySQLConnPool.cpp MySQLWorker.cpp SQLOperation.cpp -lpthread -std=c++17 -lmysqlcppconn 

void HandleQueryResult(std::unique_ptr<sql::ResultSet> result) {

    while (result->next()) {
        std::cout << "cid: " << result->getInt("cid") << ", caption: " << result->getString("caption") << std::endl;
    }

}


int main() {

    // 创建连接池
    MySQLConnPool* pool = MySQLConnPool::GetInstance("edu_svc");
    pool->InitPool("tcp://127.0.0.1:3306;xiaonie;0524", 10);

    // 创建异步处理器
    AsyncProcessor response_handler;

    // 创建查询任务
    auto query_callback = pool->Query("SELECT * FROM class;", HandleQueryResult);
    response_handler.AddQueryCallback(std::move(query_callback)); // 将查询任务添加到异步处理器中

    // 等待异步处理完成
    while (true) {
        response_handler.InvokeIfReady();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    return 0;
}

11.总结

本文从数据库连接池的概念入手,分析了同步/异步连接的区别,并详细实现了一个基于 C++ 的异步 MySQL 连接池。该连接池利用多线程 + 阻塞队列 + Promise/Future 实现了高效的异步查询,让应用线程从 I/O 等待中解放出来,提高了并发处理能力。
https://blog.csdn.net/qq_57951250/article/details/159043593?spm=1011.2124.3001.6209

https://github.com/0voice

...全文
51 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

545

社区成员

发帖
与我相关
我的任务
社区描述
零声学院,目前拥有上千名C/C++开发者,我们致力将我们的学员组织起来,打造一个开发者学习交流技术的社区圈子。
nginx中间件后端 企业社区
社区管理员
  • Linux技术狂
  • Yttsam
  • 零声教育-晚晚
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

请新加入的VIP学员,先将自己参加活动的【所有文章】,同步至社区:

【内容管理】-【同步至社区-【零声开发者社区】

试试用AI创作助手写篇文章吧