基于C++“简单且有效”的“数据库连接池”
前言
- 数据库连接池在开发中应该是很常用的一个组件,他可以很好的节省连接数据库的时间开销;
- 本文基使用C++实现了一个简单的数据库连接池,代码量只有400行只有,但是压力测试效果很好;
- 欢迎收藏 + 关注,本人将会持续更新后端与AI算法有关知识点。
文章目录
- 连接池功能点介绍
- 初始连接量
- 最大连接量
- 最大空闲时间
- 连接超时时间
- 连接池主要包含了以下功能点
- 代码
- 压力测试
MySQL 数据库是基于 C/S 模式的,在每一次访问mysql服务器的时候,都需要建立一个TCP连接,但是,在高并发情况下,大量的 TCP 三次握手、MySQL Server 连接认证、MySQL Server 关闭连接回收资源和 TCP 四次挥手所耗费的性能事件也是很明显的,故设置连接池就是为了减少这一部分的性能损耗。
连接池功能点介绍
初始连接量
表示连接池事先会和 MySQL Serve r创建最小个数的连接,当应用发起 MySQL 访问时,不用再创建和 MySQL Server 新的连接,直接从连接池中获取一个可用的连接即可,使用完成后并不去释放连接,而是把连接再归还到连接池当中。
最大连接量
当并发访问MySQL Server的请求增多时,初始连接量已经不够用了,此时会去创建更多的连接给应用去使用,但是新创建的连接数量上限是maxSize,不能无限制的创建连接。并且当这些连接使用完之后,再次归还到连接池当中来维护。
最大空闲时间
当访问MySQL的并发请求多了以后,连接池里面的连接数量会动态增加,上限是maxSize 个,当这些连接用完会再次归还到连接池当中。如果在指定的时间内这些新增的连接都没有被再次使用过,那么新增加的这些连接资源就要被回收掉,只需要保持初始连接量个连接即可。
连接超时时间
当MySQL的并发请求量过大,连接池中的连接数量已经到达最大数量了,而此时没有空闲的连接可供使用,那么此时应用从连接池获取连接无法成功,它通过阻塞的方式获取连接的时间,如果超过一个时间,那么获取连接失败。
连接池主要包含了以下功能点
- 单例模式设置连接池;
- 向用户提供一个接口,可以从池中拿到一个数据库连接;
- 采用生产者-消费者模式,池用队列作为缓冲区,实现生成连接和拿取连接;
- 采用锁,在创建、拿取连接中进行加锁;
- 采用智能指针管理从队列中获取的连接,并且采用lambda实现智能指针的析构函数,将连接从新放回队列中;
- 设置生产连接、回收连接线程,并且驻留后台,作为守护线程;
- 采用原子变量才记录当前池中的连接数。
创建目录如下:

代码
logger.h
#ifndef PUBLIC_H_
#define PUBLIC_H_#include <iostream>// 作用:封装简单的LOG
#define LOGGER(str) std::cout << "====》" << __LINE__ << " time: " << __TIME__ << " message: " << str << std::endl;#endif // !PUBLIC_H_
connection.h
#ifndef CONNECTION_H_
#define CONNECTION_H_#include <mysql/mysql.h>
#include <ctime>
#include <string>/*
功能:初始化数据库连接释放连接连接数据库查询mysql修改数据库数据刷新/设置空闲时间的起始时间点返回空闲时间
*/class Connection
{
public:// 初始化数据库连接Connection();// 释放连接~Connection();// 连接数据库bool connectionSqlBase(std::string ip, unsigned int port, std::string user, std::string passward, std::string dbName);// 查询mysqlMYSQL_RES* query(std::string sql);// 修改数据库数据bool modify(std::string sql);// 刷新/设置空闲时间的起始时间点void setStartActivateTime();// 返回空闲时间clock_t getActivateTime();
private: MYSQL* m_sqlConn{}; // 连接mysql服务器clock_t m_activateTime; // 记录空闲时间的起始点
};#endif // !CONNECTION_H_
connection.cpp
#include "connection.h"
#include "logger.h"// 初始化数据库连接
Connection::Connection()
{m_sqlConn = mysql_init(nullptr);if(m_sqlConn == nullptr) {LOGGER("mysql init false !!!");return;}
}
// 释放连接
Connection::~Connection()
{mysql_close(m_sqlConn);
}
// 连接数据库
bool Connection::connectionSqlBase(std::string ip, unsigned int port, std::string user, std::string passward, std::string dbName)
{if(nullptr == mysql_real_connect(m_sqlConn, ip.c_str(), user.c_str(), passward.c_str(), dbName.c_str(), port, NULL, 0)) {LOGGER("mysql connects error!!");return false;}return true;
}
// 查询mysql
MYSQL_RES* Connection::query(std::string sql)
{int res = mysql_query(m_sqlConn, sql.c_str());if(0 != res) {LOGGER("sql query false!!!");return nullptr;}return mysql_use_result(m_sqlConn);
}
// 修改数据库数据
bool Connection::modify(std::string sql)
{int res = mysql_query(m_sqlConn, sql.c_str());if(0 != res) {LOGGER("sql update/insert/select false!!!");return false;}return true;
}
// 刷新/设置空闲时间的起始时间点
void Connection::setStartActivateTime()
{m_activateTime = clock();
}
// 返回空闲时间
clock_t Connection::getActivateTime()
{return clock() - m_activateTime;
}
dbConnectionPool.h
#ifndef DBCONNECTION_H_
#define DBCONNECTION_H_#include "connection.h"
#include <mysql/mysql.h>
#include <queue>
#include <string>
#include <condition_variable>
#include <atomic>
#include <memory>// 核心:生产者、消费者模式class DbConnPool
{
public: // 单例模式static DbConnPool* getDbConnPool();// 对外提供接口: 获取连接的数据库,通过智能指针回收std::shared_ptr<Connection> getMysqlConn();// 测试// void test()// {// readConfigurationFile();// }private: // 单例模型:构造函数私有化, 目的:创建最小连接数量DbConnPool();DbConnPool(const DbConnPool&) = delete;DbConnPool operator=(const DbConnPool&) = delete;// 读取配置文件bool readConfigurationFile();// 如果没有Mysql连接了,则产生新连接,这个线程驻留后台(像守护线程一样)void produceNewConn();// 如果队列线程 > initSize, 且空闲时间大于最大空闲时间,则回收void recycleConn();private:// MYSQL连接信息std::string m_ip;unsigned int m_port;std::string m_username;std::string m_password;std::string m_dbname;// 数据库连接池信息int m_initSize;int m_maxSize;int m_maxFreeTime;int m_maxConnTime;// 生产者、消费者共享内存:获取连接std::queue<Connection*> m_connQueue;// 存储当前存储到队列中存储的数量std::atomic_int m_conntionCnt{};// 锁std::mutex m_queueMuetx;// 生产者、消费者:产生连接和取连接std::condition_variable m_cv; // 用于生产线程和消费线程之间的通信
};#endif // !DBCONNECTION_H_
dbConnectionPool.cpp
#include "dbConnectionPool.h"
#include "connection.h"
#include "logger.h"#include <iostream>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <thread>
#include <functional>
#include <chrono>// 创建连接:new,但是拿取连接后是交给shared_ptr// 单例模式
DbConnPool* DbConnPool::getDbConnPool()
{// 最简单的方式:staticstatic DbConnPool pool;return &pool;
}// 对外提供接口: 获取连接的数据库,通过智能指针回收
std::shared_ptr<Connection> DbConnPool::getMysqlConn()
{std::unique_lock<std::mutex> lock(m_queueMuetx);// 判断队列是否为空while(m_connQueue.empty()) {// 队列为空,则等待 最大连接时间, 即这个时候客户端请求连接,但是池里没有连接了,则会等待,如果超过了最大时间,则:连接失败if(std::cv_status::timeout == m_cv.wait_for(lock, std::chrono::seconds(m_maxConnTime))) {// 再次判断是否为空if(m_connQueue.empty()) {LOGGER("no conntion !!!!");return nullptr;}}}/*从队列中获取一个连接,交给**智能指针管理**注意:删除连接有回收线程监控,而这里获取的连接使用完后,需要还给**队列中**,所以**需要重写智能指针的回收函数***/std::shared_ptr<Connection> sp(m_connQueue.front(), [&](Connection* pconn){// 注意,这里需要加锁std::unique_lock<std::mutex> lock(m_queueMuetx);pconn->setStartActivateTime();m_connQueue.push(pconn); // 入队m_conntionCnt++; // +1});// 弹出队列m_connQueue.pop();m_conntionCnt--; // -1return sp;
}// 单例模型:构造函数私有化
DbConnPool::DbConnPool()
{if(readConfigurationFile() == false) {return;}std::unique_lock<std::mutex> lock(m_queueMuetx);for(int i = 0; i < m_maxSize; i++) {Connection* newConn = new Connection();newConn->connectionSqlBase(m_ip, m_port, m_username, m_password, m_dbname);newConn->setStartActivateTime(); // 设置 空闲时间 的起始点m_connQueue.push(newConn); // 入队m_conntionCnt++; // 存储到队列中数据+1}// 开启线程:检查是否需要需要**新创建连接**std::thread produce(std::bind(&DbConnPool::produceNewConn, this));produce.detach(); // 驻留后台// 开启线程,检查是否需要**删除连接**std::thread search(std::bind(&DbConnPool::recycleConn, this));search.detach(); // 驻留后台
}// 读取配置文件
bool DbConnPool::readConfigurationFile()
{FILE* fp = fopen("./mysql.ini", "r");if(fp == nullptr) {LOGGER("mysql.ini open false!!");return false;}char buff[BUFSIZ] = { 0 };while(!feof(fp)) {// clearmemset(buff, 0, sizeof(buff));// 读取fgets(buff, BUFSIZ, fp);std::string str = buff;// 判空if(str.empty()) {continue;}// 截断int idx = str.find('=', 0);if(idx == -1) {continue;}int end = str.find('\n', idx);std::string key = str.substr(0, idx);std::string value = str.substr(idx + 1, end - idx - 1);//std::cout << "key: " << key << " value: " << value << std::endl;if(key == "ip") {m_ip = value;} else if(key == "port") {m_port = atoi(value.c_str());} else if(key == "username") {m_username = value;} else if(key == "password") {m_password = value;} else if(key == "dbname") {m_dbname = value;} else if(key == "initSize") {m_initSize = atoi(value.c_str());} else if(key == "maxSize") {m_maxSize = atoi(value.c_str());} else if(key == "maxFreeTime") {m_maxFreeTime = atoi(value.c_str());} else if(key == "maxConnTime") {m_maxConnTime = atoi(value.c_str());}}std::cout << m_ip << " " << m_port << " " << m_username << " " << m_password << " " << m_dbname << " " << m_initSize << " " << m_maxSize << " " << m_maxFreeTime << " " << m_maxConnTime << std::endl;return true;
}// 如果池里没有Mysql连接了,则产生新连接,这个线程驻留后台(像守护线程一样)
/*
实现思路:设置一个循环:循环检查如果队列不为空,则条件变量一直等待
*/
void DbConnPool::produceNewConn()
{for(;;) {std::unique_lock<std::mutex> lock(m_queueMuetx);while(!m_connQueue.empty()) {m_cv.wait(lock); // 条件变量一直等待}// 这个时候,队列为空,从新创建连接for(int i = 0; i < m_maxSize; i++) {Connection* newConn = new Connection();newConn->setStartActivateTime(); // 刷新时间m_connQueue.push(newConn);m_conntionCnt++; // +1}// 通知等待线程m_cv.notify_all();}
}// 如果队列线程 > initSize, 且空闲时间大于最大空闲时间,则回收
void DbConnPool::recycleConn()
{for(;;) {std::unique_lock<std::mutex> lock(m_queueMuetx);while(m_conntionCnt > m_initSize) {Connection* conn = m_connQueue.front();// 超过最大空闲时间if((static_cast<double>(conn->getActivateTime()) / CLOCKS_PER_SEC) > m_maxFreeTime) {m_connQueue.pop();m_conntionCnt--;delete conn;} else { // 对头没超过,则直接退出break;}}}
}
压力测试
分别插入10000条数据,对没有使用连接池和使用连接池分别进行测试。
#include "dbConnectionPool.h"
#include "connection.h"
#include <iostream>
#include <mysql/mysql.h>
#include <chrono>int main() {auto start = std::chrono::high_resolution_clock::now(); // 获取当前时间点for(int i = 0; i < 10000; i++) {
#if 1DbConnPool* pool = DbConnPool::getDbConnPool();std::shared_ptr<Connection> conn = pool->getMysqlConn();char sql[1024] = { 0 };sprintf(sql, "insert into temp(name, age, sex) values('%s', '%d', '%s');", "yxz", 18, "man");conn->modify(sql);
#elif 0Connection conn;conn.connectionSqlBase("127.0.0.1", 3306, "root", "wy2892586", "test");char sql[1024] = { 0 };sprintf(sql, "insert into temp(name, age, sex) values('%s', '%d', '%s');", "yxz", 18, "man");conn.modify(sql);
#endif}auto end = std::chrono::high_resolution_clock::now(); // 获取结束时间点std::chrono::duration<double, std::milli> duration = end - start; // 计算持续时间,并转换为毫秒std::cout << "Time: " << duration.count() << " ms" << std::endl;return 0;
}
结果如下:

相关文章:
基于C++“简单且有效”的“数据库连接池”
前言 数据库连接池在开发中应该是很常用的一个组件,他可以很好的节省连接数据库的时间开销;本文基使用C实现了一个简单的数据库连接池,代码量只有400行只有,但是压力测试效果很好;欢迎收藏 关注,本人将会…...
为什么要将PDF转换为CSV?CSV是Excel吗?
在企业和数据管理的日常工作中,PDF文件和CSV文件承担着各自的任务。PDF通常用于传输和展示静态的文档,而CSV因其简洁、易操作的特性,广泛应用于数据存储和交换。如果需要从PDF中提取、分析或处理数据,转换为CSV格式可能是一个高效…...
Redis 集群的三种模式:一主一从、一主多从和多主多从
本文记述了博主在学习 Redis 在大型项目下的使用方式,包括如何设置Redis主从节点,应对突发状况如何处理。在了解了Redis的集群搭建和相关的主从复制以及哨兵模式的知识以后,进而想要了解 Redis 集群如何使用,如何正确使用…...
面试题——简述Vue 3的服务器端渲染(SSR)是如何工作的?
面试题——简述Vue3的服务器端渲染(SSR)是如何工作的? 服务器端渲染(SSR)已经成为了一个热门话题。Vue 3,作为一款流行的前端框架,也提供了强大的SSR支持。那么,Vue 3的SSR究竟是如…...
2.25DFS和BFS刷题
洛谷P1101单词方阵:用sta存字符串,for找到‘y的位置,然后dfs对字符串用for进行一个一个的判断,不符合就return,下面再用for进行book标记,能执行下面的for说明上面没有return,所以说明找到&#…...
C语言基本知识------指针(4)
1. 回调函数是什么? 回调函数就是⼀个通过函数指针调用的函数。 如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数 时,被调⽤的函数就是回调函数。 void qsort(void base,//指针…...
【OMCI实践】ONT上线过程的omci消息(六)
引言 在前四篇文章中,主要介绍了ONT上线过程的OMCI交互的第一、二、三个阶段omci消息,本篇介绍第四个阶段,OLT下发配置到ONT。前三个阶段,每个厂商OLT和ONT都遵循相同标准,OMCI的交换过程大同小异。但第四个阶段&…...
C语言(13)------------>do-while循环
1.do-while循环的语法 我们知道C语言有三大结构,顺序、选择、循环。我们可以使用while循环、for循环、do-while循环实现循环结构。之前的博客中提及到了前两者的技术实现。可以参考: C语言(11)------------->while循…...
腾讯SQL面试题解析:如何找出连续5天涨幅超过5%的股票
腾讯SQL面试题解析:如何找出连续5天涨幅超过5%的股票 作者:某七年数据开发工程师 | 2025年02月23日 关键词:SQL窗口函数、连续问题、股票分析、腾讯面试题 一、问题背景与难点拆解 在股票量化分析场景中,"连续N天满足条件"是高频面试题类型。本题要求在单表stoc…...
HybridCLR+Adressable+Springboot热更
本文章会手把手教大家如何搭建HybridCLRAdressableSpringboot热更。 创作不易,动动发财的小手点个赞。 安装华佗 首先我们按照官网的快速上手指南搭建一个简易的项目: 快速上手 | HybridCLR 注意在热更的代码里添加程序集。把用到的工具放到程序集里…...
电脑连接示波器显示波形
通过网线连接示波器和电脑,将示波器波形显示在电脑上直接复制图片至报告中,以下是配置步骤。 一、设备 网线,Tektronix示波器,电脑 二、使用步骤 1.用网线连接电脑和示波器 2.电脑关掉WiFi,查看IPv4网关地址…...
监听其他音频播放时暂停正在播放的音频
要实现当有其他音频播放时暂停当前音频,你可以使用全局事件总线或 Vuex 来管理音频播放状态。这里我将展示如何使用一个简单的事件总线来实现这个功能。 首先,你需要创建一个事件总线。你可以在项目的一个公共文件中创建它,例如 eventBus.js…...
小熊猫C++安装EasyX最新教程
1.下载EasyX 官网下载: EasyX 官网https://easyx.cn/ 2.将下载文件改格式解压 注意:下载文件为.exe格式,需将其格式改成.zip格式! 如何改格式? a.若文件名字未显示.exe (1).打开此电脑 (2).点击上端的查看 (…...
安装VM和Centos
安装VM 一、打开虚拟机 二、选择典型 三、选择光盘 四、指定虚拟机位置 五、设置磁盘大小并拆分为多个文件 六、完成 安装Centos 一、上述过程完成后我们直接打开虚拟机 二、语言选择中文 三、默认安装位置并点击完成 四、点击开始安装 五、点击设置密码 设置完密码后点击完成…...
git 命令 设置别名
在Git中,您可以通过以下命令查看所有的alias(别名): git config --get-regexp alias 这个命令会列出所有配置的alias,例如: alias.st.status alias.co.checkout alias.br.branch ... 如果您想查看某个特定a…...
React + TypeScript 全栈开发最佳实践
React TypeScript 全栈开发最佳实践 一、环境搭建与项目初始化 node.js和npm的安装请参考我的文章。 1.1 脚手架选择与工程创建 # 使用Vite 5.x创建ReactTS项目(2025年主流方案) npx create-vitelatest my-app --template react-ts cd my-app npm in…...
springboot志同道合交友网站设计与实现(代码+数据库+LW)
摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本志同道合交友网站就是在这样的大环境下诞生,其可以帮助使用者在短时间内处理完毕庞大的数据信…...
防火墙双机热备---VRRP,VGMP,HRP(超详细)
双机热备技术-----VRRP,VGMP,HRP三个组成 注:与路由器VRRP有所不同,路由器是通过控制开销值控制数据包流通方向 防火墙双机热备: 1.主备备份模式 双机热备最大的特点就是防火墙提供了一条专门的备份通道(心…...
MQTT实现智能家居------4、在Linux上运行MQTT
进入主目录,创建一个MQTT文件夹 cd ~ mkdir MQTT 用FileZilla连接开发板,将我发布的压缩包解压以后放进MQTT 安装cmake sudo apt-get install cmake g编译 & 运行 echo sudo apt-get update >> build.sh #向build.sh文件写入内容 chmod…...
VMware建立linux虚拟机
本文适用于初学者,帮助初学者学习如何创建虚拟机,了解在创建过程中各个选项的含义。 环境如下: CentOS版本: CentOS 7.9(2009) 软件: VMware Workstation 17 Pro 17.5.0 build-22583795 1.配…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
GraphQL 实战篇:Apollo Client 配置与缓存
GraphQL 实战篇:Apollo Client 配置与缓存 上一篇:GraphQL 入门篇:基础查询语法 依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在: https://github.com/GoldenaArcher/graphql…...
ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...
