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

news2025/1/23 3:45:51

目背景
常见的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://img-blog.csdnimg.cn/direct/5b15db6b9ade48b5b6f65b061a45b200.png参考
https://zhuanlan.zhihu.com/p/616675628

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1344144.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Bootstrap学习 day4】

Bootstrap5 列表组 使用Bootstrap创建列表 可以创建三种不类型的HTML列表&#xff1a; 无序列表—顺序无关紧要的项目列表。无序列表中的列表标有项目符号&#xff0c;例如。、等ul>li有序列表—顺序确实很重要的项目列表。有序列表中的列表项用数字标记&#xff0c;例如1、…

欧洲十大跨境电商平台,自养号测评下单的重要性及优势

在欧洲站&#xff0c;用户体量非常庞大&#xff0c;这与近几年人们的消费习惯密不可分&#xff0c;越来越多的人开始网购&#xff0c;据欧盟委员的最新调研显示&#xff0c;在欧盟&#xff0c;近一半(42%)的中小企业通过在线市场销售产品和服务。 所以&#xff0c;逸居海外给大…

Grafana无法发送告警消息的飞书webhook(机器人)

1.问题描述 Grafana无法向飞书机器人发送报警消息&#xff0c;实测使用Grafana自带的webhook也不好使&#xff0c;对于用飞书办公的程序猿非常不便&#xff0c;后来发现一个报警神器&#xff0c;开源免费&#xff0c;关键是好用 PrometheusAlert 2.PrometheusAlert安装 Prom…

ansible_角色的使用

本章主要介绍ansible中角色的使用 了解什么是角色独立地写一个角色使用角色系统自带角色地使用 1.了解角色 正常情况下&#xff0c;配置一个服务如 apache时&#xff0c;要做一系列的操作:安装、拷贝、启动服务等。如果要在不同的机器上重复配置此服务&#xff0c;需要重新执…

企业私有云容器化架构

什么是虚拟化: 虚拟化&#xff08;Virtualization&#xff09;技术最早出现在 20 世纪 60 年代的 IBM 大型机系统&#xff0c;在70年代的 System 370 系列中逐渐流行起来&#xff0c;这些机器通过一种叫虚拟机监控器&#xff08;Virtual Machine Monitor&#xff0c;VMM&#x…

IC入门必备!数字IC中后端设计实现全流程解析(1.3万字长文)

吾爱IC社区自2018年2月份开始在公众号上开始分享数字IC后端设计实现相关基础理论和实战项目经验&#xff0c;累计输出文字超1000万字。全部是小编一个个字敲出来的&#xff0c;绝对没有复制粘贴的情况&#xff0c;此处小编自己得给自己鼓鼓掌鼓励下自己。人生不要给自己设限&am…

【华为数据之道学习笔记】7-5通过感知能力推进企业业务数字化

感知数据在华为信息架构中的位置 感知可以应用于广泛的物理世界和数字世界&#xff0c;感知范围可以从人、物、作业、地点扩展到复杂环境。成熟的用例倾向于以物和人为中心。而在企业中&#xff0c;只有将感知数据纳入整体的数据体系中&#xff0c;才能发挥感知数据的价值。 华…

Java核心技术卷接口的实现与继承多态知识梳理总结

Java核心技术卷接口的实现与继承多态知识梳理总结 接口的概念 在Java程序设计语言中&#xff0c;接口不是类&#xff0c;而是对希望符合这个接口的类的一组需求。 form&#xff1a; Java核心技术卷 I&#xff08;原书第11版&#xff09; 基础知识 by 凯 S.霍斯特曼 在Java中&a…

园林机械部件自动化三维测量检测形位公差-CASAIM自动化三维检测工作站

随着园林机械的广泛应用&#xff0c;对其机械部件的精确测量需求也日益增加。传统的测量方法不仅效率低下&#xff0c;而且精度难以保证&#xff0c;因此&#xff0c;自动化三维测量技术成为了解决这一问题的有效途径。本文将重点介绍CASAIM自动化三维检测工作站在园林机械部件…

线性代数笔记1 12.30

学习视频&#xff1a; 1.4 行列式的计算&#xff08;一&#xff09;_哔哩哔哩_bilibili 以下内容&#xff0c;包含&#xff1a; 二阶三阶行列式 n阶行列式 行列式的性质 行列式按行展开

PiflowX组件-WriteToKafka

WriteToKafka组件 组件说明 将数据写入kafka。 计算引擎 flink 有界性 Streaming Append Mode 组件分组 kafka 端口 Inport&#xff1a;默认端口 outport&#xff1a;默认端口 组件属性 名称展示名称默认值允许值是否必填描述例子kafka_hostKAFKA_HOST“”无是逗号…

9种卷积注意力机制创新方法汇总,含2024最新

今天咱们来聊聊卷积注意力机制。 相信各位在写论文的时候都苦恼过怎么更好地改模型&#xff0c;怎么更高效地提高模型的性能和泛化能力吧&#xff1f;我的建议是&#xff0c;不妨考虑考虑卷积注意力。 卷积注意力机制是一种通过关注输入数据中的不同部分来改进模型性能的方法…

数据结构之树 --- 二叉树

目录 定义二叉树的结构体 二叉树的遍历 递归遍历 非递归遍历 链式二叉树的实现 二叉树的功能接口 先序遍历创建二叉树 后序遍历销毁二叉树 先序遍历查找树中值为x的节点 层序遍历 上篇我们对二叉树的顺序存储堆进行了讲述&#xff0c;本文我们来看链式二叉树。 定…

台式电源质量如何检测?纳米软件为您科普

一、外观检测 观察台式机电脑电源外观是否有损伤、烧焦&#xff0c;电源线是否有破损、短线的情况。观察电源的电压、电流、功率等参数&#xff0c;是否符合台式机电脑。 二、直观检测 开通电源&#xff0c;如果所有指示灯不亮&#xff0c;风扇没有声音&#xff0c;电源损坏的可…

yolov5 主要流程

1.介绍 本文包含了有关yolov5目标检测的基本流程&#xff0c;包括模型训练与模型部署&#xff0c;旨在帮助小伙伴们建立系统的认知&#x1f496;&#x1f496; YOLO是 "You only look once "的首字母缩写&#xff0c;是一个开源软件工具&#xff0c;它具有实时检测…

Mysql高阶语句及存储过程

目录 空值(NULL) 和 无值() 的区别&#xff1a; 正则表达式&#xff1a; 存储过程&#xff1a; 创建存储过程&#xff1a; 存储过程的参数&#xff1a; 存储过程的控制语句&#xff1a; mysql高阶语句 case是 SQL 用来做为if&#xff0c;then&#xff0c;else 之类逻辑的…

php-fpm运行一段时间,内存不足

目录 一&#xff1a;原因分析 二&#xff1a;解决 三:观察系统情况 php-fpm运行一段时间&#xff0c;内存不足&#xff0c;是什么原因呢。 一&#xff1a;原因分析 1:首先php-fpm的配置 &#xff08;1&#xff09;启动的进程数 启动的进程数越多,占用内存越高; 2:其次…

Android studio CMakeLists.txt 打印的内容位置

最近在学习 cmake 就是在安卓中 , 麻烦的要死 , 看了很多的教程 , 发现没有 多少说对打印位置在哪里 , 先说一下版本信息 , 可能你们也不一样 gradle 配置 apply plugin: com.android.applicationandroid {compileSdkVersion 29buildToolsVersion "29.0.3"defau…

2023开发原子开放者大会:AI时代的前端开发,挑战与机遇并存

前言 12月16日&#xff0c;以“一切为了开发者”为主题的开放原子开发者大会在江苏省无锡市开幕。江苏省工业和信息化厅厅长朱爱勋、中国开源软件推进联盟主席陆首群等领导和专家参加开幕式&#xff0c;工业和信息化部信息技术发展司副司长王威伟、江苏省工业和信息化厅副厅长…

视频流媒体直播云服务管理平台EasyNVS长时间运行出现崩溃情况是什么原因?该如何解决?

EasyNVS云管理平台具备汇聚与管理EasyGBS、EasyNVR等平台的能力&#xff0c;可以将接入的视频资源实现统一的视频能力输出&#xff0c;支持远程可视化运维等管理功能&#xff0c;还能解决设备现场没有固定公网IP却需要在公网直播的需求。 有用户反馈&#xff0c;在长时间不间断…