基于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.配…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...