[mysql 基于C++实现数据库连接池 连接池的使用] 持续更新中

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: [mysql 基于C++实现数据库连接池 连接池的使用] 持续更新中

目背景

常见的MySQL、Oracle、SQLServer等数据库都是基于C/S架构设计的,即(客户端/服务器)架构,也就是说我们对数据库的操作相当于一个客户端,这个客户端使用既定的API把SQL语句通过网络发送给服务器端,MySQL Server执行完SQL语句后将结果通过网络返回客户端。通过网络通信的话就要涉及到TCP/IP协议里的“三次握手”、“四次挥手”等,大量访问时,每一个用户的请求都会对应一次“三次握手”、“四次挥手”的过程,这个性能的消耗是相当严重的;

对于数据库本质上是对磁盘的操作,如果对数据库的访问过多,即(I/O)操作过多,会出现访问瓶颈。

而常见的解决数据库访问瓶颈的方法有两种:

一、为减少磁盘 I/O的次数,在数据库和服务器的应用中间加一层 缓存数据库(例如:Redis、Memcache);

二、增加 连接池,来减少高并发情况下大量 TCP三次握手、MySQL Server连接认证、MySQL Server关闭连接回收资源和TCP四次挥手 所耗费的性能。

mysqlconn.hpp 实现连接 增删改查操作

#include <mysql/mysql.h>
#include <iostream>
#include <string>
#include <ctime>
#include <chrono>
#include <memory> 
#define INFO    1
#define WARNING 2
#define ERROR   3
#define FATAL   4
#define LOG(level, message) Log(#level, message, __FILE__, __LINE__)
void Log(std::string level, std::string message, std::string file_name, int line)
{
  std::cout<<"["<<level<<"]["<<time(nullptr)<<"]["<<message<<"]["<<file_name<<"]["<<line<<"]"<<std::endl;
}
class mysqlconn{
private:
  MYSQL *m_conn = nullptr;
  MYSQL_RES* m_res = nullptr;//查询结果集
  MYSQL_ROW m_row;//记录结构体
  void freeResult()
  {
      if(m_res)
      {
          mysql_free_result(m_res);
          m_res = nullptr;
      }
  }
  std::chrono::steady_clock::time_point m_aliveTime;
public:
  mysqlconn()
  {
    //获取一个MYSQL句柄
    m_conn = mysql_init(nullptr);
    //设置字符集
    mysql_set_character_set(m_conn,"utf8");
  }
  ~mysqlconn()
  {
    freeResult();
    if(m_conn != nullptr)
      {
          mysql_close(m_conn);
      }
  }
  bool query(std::string sql){
    freeResult();
    if(mysql_query(m_conn, sql.c_str())){
      return false;
    }
    m_res = mysql_store_result(m_conn);
    return true;
  }
  //更新 修改 删除
  bool update(std::string sql){
    return mysql_query(m_conn, sql.c_str());
  }
  //连接指定的数据库
    bool connect(std::string ip, std::string user, std::string passwd, std::string dbName,  unsigned int port)
    {
      return mysql_real_connect(m_conn, ip.c_str(), user.c_str(), passwd.c_str(), dbName.c_str(), port,nullptr,0) != nullptr;
  }
      //遍历得到的结果集
    bool next()
    {
    if(m_res != nullptr)
      {
          m_row = mysql_fetch_row(m_res);  //获取一行
          if(m_row != nullptr)
          {
              return true;
          }
      }
      return false;
  }
    //获取结果集里的值
    std::string value(int index){
      int rowCount = mysql_num_fields(m_res);  //返回结果集中字段数目
      if(index >= rowCount || index < 0)
      {
          return std::string();
      }
      char* ans = m_row[index];
      unsigned long length = mysql_fetch_lengths(m_res)[index];
      return std::string(ans,length);   
  }
    //事务处理提交方式
    bool transaction()
  {
    return mysql_autocommit(m_conn,false);
  }
    //事务提交
    bool commit()
    {
      return mysql_commit(m_conn);
  }
    //事务回滚
    bool rollback()
  {
    return mysql_rollback(m_conn);
  }
    //更新空闲时间点
    void refreshAliveTime(){
    m_aliveTime = std::chrono::steady_clock::now();
  }
    //计算连接空闲时长
    long long getAliveTime()
    {
      std::chrono::duration<double> diff = std::chrono::steady_clock::now() - m_aliveTime;       //nanosecods 纳秒
      return diff.count();
  }
};

connpool.hpp 连接池

#include <mutex>
#include <condition_variable>
#include <queue>
#include <fstream>
#include <thread>
#include "mysqlconn.hpp"
class ConnectionPool
{
private:
    std::string m_user;
    std::string m_passwd;
    std::string m_ip;
    std::string m_dbName;
    unsigned short m_port;
    //连接的上限和下限,自动维护线程池的连接数
    int m_minSize;
    int m_maxSize;
    //连接的超时时长
    int m_timeout;
    int m_maxIdleTime;
    //线程同步  
    std::mutex m_mutexQ;                     //互斥锁
    std::condition_variable m_cond;          //条件变量
    std::queue<mysqlconn *> m_connectionQ;    //共享资源
public:
    //对外接口,获取线程池
    //静态局部变量是线程安全的
    static ConnectionPool  *getConnectPool()    
    {
    static ConnectionPool pool;
      return &pool;
  }
    //获取线程池中的连接
    std::shared_ptr<mysqlconn>  getConnection()
    {
    //需要操作共享资源
    std::unique_lock<std::mutex> locker(m_mutexQ);
    //判断连接池队列为空
    while(m_connectionQ.empty())
    {
      if(std::cv_status::timeout == m_cond.wait_for(locker, std::chrono::milliseconds(m_timeout)))
      {
        if(m_connectionQ.empty())
        {
          continue;
        }
      }
    }
    //自定义shared_ptr析构方法,重新将连接放回到连接池中,而不是销毁
    std::shared_ptr<mysqlconn> connptr(m_connectionQ.front(),[this](mysqlconn *conn){
    std::unique_lock<std::mutex> locker(m_mutexQ);
    conn->refreshAliveTime();
    m_connectionQ.push(conn); 
    });
    //弹出,放到了队尾
    m_connectionQ.pop();
    m_cond.notify_all();
    return connptr;
  }
    //防止外界通过拷贝构造函数和移动拷贝构造函数
    ConnectionPool(const ConnectionPool &obj) = delete;
    ConnectionPool& operator=(const ConnectionPool& obj) = delete;
    ~ConnectionPool()
  {
    while(!m_connectionQ.empty())
    {
        mysqlconn *conn = m_connectionQ.front();
        m_connectionQ.pop();
        delete conn;
    }
  }
private:
    //构造函数私有化
    ConnectionPool()
    {
    //加载配置文件
    if(!parseJsonFile())
    {
      return;
    }
    //创建最少连接数
    for(int i=0;i<m_minSize;++i)
    {
      addConnect();
    }
    //创建子线程用于检测并创建新的连接
    std::thread producer(&ConnectionPool::produceConnection,this);
    //销毁连接,检测并销毁连接
    std::thread recycler(&ConnectionPool::recycleConnection,this);
    //设置线程分离
    producer.detach();
    recycler.detach();
  }
    //解析配置文件
    bool parseJsonFile(){    //可以通过配置文件配置数据 这里写死 
    m_ip      = "127.0.0.1";
        m_user    = "pig";
        m_passwd  = "test1234";
        m_dbName  = "test";
        m_port    = 3306;
        m_minSize = 10;
        m_maxSize = 100;
        m_timeout = 10;
        m_maxIdleTime = 20;
    return true;
  }
    //任务函数
    void produceConnection()   //生产数据库连接
    {
      //通过轮询的方式不断的去检测
      while(true) 
      {
          //操作共享资源,需要加锁
          std::unique_lock<std::mutex> locker(m_mutexQ);
          //判断连接数是否达到容量,如果大于等于容量则需要阻塞一段时间
          while (m_connectionQ.size() >= m_maxSize)   
          {
             m_cond.wait(locker);
          }
          addConnect();
          m_cond.notify_all();        //唤醒消费者
      }
  }
    void recycleConnection()   //销毁数据库连接
    {
    while(true)
    {
       //休眠一定的时长
       std::this_thread::sleep_for(std::chrono::milliseconds(500));
       std::unique_lock<std::mutex> locker(m_mutexQ);
       //让线程池中最少保持用于 m_minSize个线程
       while(m_connectionQ.size() > m_minSize)
       {
            mysqlconn *recyConn = m_connectionQ.front();
            //如果超时则销毁
            if(recyConn->getAliveTime() >= m_maxIdleTime)
            {
                m_connectionQ.pop();
                delete recyConn;
            } 
            else
            {
                break;
            }
       }
      }
  }
    void addConnect()         //添加连接
    {
    mysqlconn *conn = new mysqlconn;
    conn->connect(m_ip,m_user,m_passwd,m_dbName,m_port);
      conn->refreshAliveTime();
      m_connectionQ.push(conn);
  }
};

main.cpp 测试主函数 单线程 连接池 多线程连接池

#include "connpool.hpp"
void pthread1_no_pool()
{
  clock_t begin = clock();
  std::unique_ptr<mysqlconn> sp = std::make_unique<mysqlconn>();
  bool connflag = sp->connect("127.0.0.1","pig","test1234", "test",3306);
  if(connflag == false) return;
  for (int i = 0; i < 4 * 1000; ++i)
  {
      sp->refreshAliveTime();
    char sql[1024] = { 0 };
    sprintf(sql, "insert into tb_file values('%d','%s','%s');",
      i, "pthread1_no_pool", "1.png");
    auto upflag = sp->update(sql);
  }
  clock_t end = clock();
  std::cout << "pthread1_no_pool:" << (end - begin) << "ms" << std::endl;
}
void pthread1_use_pool(){
  ConnectionPool *cp = ConnectionPool::getConnectPool();
  clock_t begin = clock();
  std::shared_ptr<mysqlconn> sp = cp->getConnection();
  for (int i = 0; i < 1000 * 4; ++i)
  {
    char sql[1024] = { 0 };
    sprintf(sql, "insert into tb_file(id, name, file) values('%d','%s','%s');",
      i, "pthread1_use_pool", "1.png");
    sp->update(sql);
  }
  clock_t end = clock();
  std::cout <<"pthread1_use_pool:" << (end - begin) << "ms" << std::endl;
}
void pthread4_no_pool()
{
  clock_t begin = clock();
  std::thread tt[4];
  for(int n = 0; n < 4; n++){
    tt[n] = std::thread([=]{
      std::unique_ptr<mysqlconn> sp = std::make_unique<mysqlconn>();
      sp->connect("127.0.0.1","pig","test1234", "test",3306);
      for (int i = 0; i < 1000 * (n + 1); ++i)
      {
          sp->refreshAliveTime();
        char sql[1024] = { 0 };
        sprintf(sql, "insert into tb_file values('%d','%s','%s');",
          i, "pthread1_no_pool", "1.png");
        sp->update(sql);
      }
    });
  }
  for(int i = 0; i < 4; i++){
    tt[i].join();
  }
  clock_t end = clock();
  std::cout <<"pthread4_no_pool:" << (end - begin) << "ms" << std::endl;
}
void work(ConnectionPool *cp , int l){
  std::shared_ptr<mysqlconn> sp = cp->getConnection();
  for (int i = l * 1000; i < 1000 * (l + 1); ++i)
  {
    char sql[1024] = { 0 };
    sprintf(sql, "insert into tb_file values('%d','%s','%s');",
      i, "pthread1_use_pool", "1.png");
    auto upflag = sp->update(sql);
    if(upflag != 0)
    {
      std::cout <<"pthread4_use_pool:" << upflag << sql << std::endl;
      continue;
    }
  }
}
void pthread4_use_pool()
{
  ConnectionPool *cp = ConnectionPool::getConnectPool();
  clock_t begin = clock();
  std::thread tt[4];
  for(int i = 0; i < 4; i++){
    tt[i] = std::thread(work, cp, i);
  }
  for(int i = 0; i < 4; i++){
    tt[i].join();
  }
  clock_t end = clock();
  std::cout <<"pthread4_use_pool:" << (end - begin) << "ms" << std::endl;
}
// g++ -o main main.cpp connpool.hpp mysqlconn.hpp -lmysqlclient -std=c++14 -lpthread
int main()
{
  /*单线程 不使用连接池*/
  //LOG(INFO, "pthread1_no_pool test:");
  //pthread1_no_pool();
  /*单线程 使用连接池*/
  //LOG(INFO, "pthread1_use_pool test:");
  //pthread1_use_pool();
  /*多线程 不使用连接池*/
  LOG(INFO, "pthread4_no_pool test:");
  pthread4_no_pool();
  /*多线程 使用连接池*/
  //LOG(INFO, "pthread4_use_pool test:");
  //pthread4_use_pool();
  return 0;
}

单线程 无连接池 4000条数据插入

单线程 连接池 4000条数据插入

4线程 无连接池

4线程 连接池

测试结果 和预期一样 多线程下使用连接池中的连接 比重复建立连接快很多

![结果](https://ucc.alicdn.com/images/user-upload-01/direct/5b15db6b9ade48b5b6f65b061a45b200.png参考

https://zhuanlan.zhihu.com/p/616675628

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
9天前
|
存储 Oracle 关系型数据库
数据库传奇:MySQL创世之父的两千金My、Maria
《数据库传奇:MySQL创世之父的两千金My、Maria》介绍了MySQL的发展历程及其分支MariaDB。MySQL由Michael Widenius等人于1994年创建,现归Oracle所有,广泛应用于阿里巴巴、腾讯等企业。2009年,Widenius因担心Oracle收购影响MySQL的开源性,创建了MariaDB,提供额外功能和改进。维基百科、Google等已逐步替换为MariaDB,以确保更好的性能和社区支持。掌握MariaDB作为备用方案,对未来发展至关重要。
34 3
|
9天前
|
安全 关系型数据库 MySQL
MySQL崩溃保险箱:探秘Redo/Undo日志确保数据库安全无忧!
《MySQL崩溃保险箱:探秘Redo/Undo日志确保数据库安全无忧!》介绍了MySQL中的三种关键日志:二进制日志(Binary Log)、重做日志(Redo Log)和撤销日志(Undo Log)。这些日志确保了数据库的ACID特性,即原子性、一致性、隔离性和持久性。Redo Log记录数据页的物理修改,保证事务持久性;Undo Log记录事务的逆操作,支持回滚和多版本并发控制(MVCC)。文章还详细对比了InnoDB和MyISAM存储引擎在事务支持、锁定机制、并发性等方面的差异,强调了InnoDB在高并发和事务处理中的优势。通过这些机制,MySQL能够在事务执行、崩溃和恢复过程中保持
34 3
|
9天前
|
SQL 关系型数据库 MySQL
数据库灾难应对:MySQL误删除数据的救赎之道,技巧get起来!之binlog
《数据库灾难应对:MySQL误删除数据的救赎之道,技巧get起来!之binlog》介绍了如何利用MySQL的二进制日志(Binlog)恢复误删除的数据。主要内容包括: 1. **启用二进制日志**:在`my.cnf`中配置`log-bin`并重启MySQL服务。 2. **查看二进制日志文件**:使用`SHOW VARIABLES LIKE &#39;log_%&#39;;`和`SHOW MASTER STATUS;`命令获取当前日志文件及位置。 3. **创建数据备份**:确保在恢复前已有备份,以防意外。 4. **导出二进制日志为SQL语句**:使用`mysqlbinlog`
49 2
|
23天前
|
关系型数据库 MySQL 数据库
Python处理数据库:MySQL与SQLite详解 | python小知识
本文详细介绍了如何使用Python操作MySQL和SQLite数据库,包括安装必要的库、连接数据库、执行增删改查等基本操作,适合初学者快速上手。
159 15
|
16天前
|
SQL 关系型数据库 MySQL
数据库数据恢复—Mysql数据库表记录丢失的数据恢复方案
Mysql数据库故障: Mysql数据库表记录丢失。 Mysql数据库故障表现: 1、Mysql数据库表中无任何数据或只有部分数据。 2、客户端无法查询到完整的信息。
|
23天前
|
关系型数据库 MySQL 数据库
数据库数据恢复—MYSQL数据库文件损坏的数据恢复案例
mysql数据库文件ibdata1、MYI、MYD损坏。 故障表现:1、数据库无法进行查询等操作;2、使用mysqlcheck和myisamchk无法修复数据库。
|
27天前
|
SQL 关系型数据库 MySQL
MySQL导入.sql文件后数据库乱码问题
本文分析了导入.sql文件后数据库备注出现乱码的原因,包括字符集不匹配、备注内容编码问题及MySQL版本或配置问题,并提供了详细的解决步骤,如检查和统一字符集设置、修改客户端连接方式、检查MySQL配置等,确保导入过程顺利。
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
57 2
|
2月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
109 5
|
2月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
107 4