基于C++“简单且有效”的“数据库连接池”

news2025/2/26 19:05:43

前言

  • 数据库连接池在开发中应该是很常用的一个组件,他可以很好的节省连接数据库的时间开销;
  • 本文基使用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);
    // 查询mysql
    MYSQL_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()
{
    // 最简单的方式:static
    static 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--;   // -1

    return 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)) {
        // clear
        memset(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 1
        DbConnPool* 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  0
        Connection 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;
}

结果如下

在这里插入图片描述

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

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

相关文章

国产编辑器EverEdit - 洞察秋毫!文件比较功能!

1 文件比较 1.1 应用场景 项目开发过程中&#xff0c;可能不同的部分会由不同的人在负责&#xff0c;存在一个文件多人编辑的情况&#xff0c;用户需要寻找差异&#xff0c;并将文档进行合并&#xff0c;比较专业的文本比较工具为BeyondCompare&#xff0c;WinMerge等。   如…

QARepVGG--含demo实现

文章目录 前言引入Demo实现总结 前言 在上一篇博文RepVGG中&#xff0c;介绍了RepVGG网络。RepVGG 作为一种高效的重参数化网络&#xff0c;通过训练时的多分支结构&#xff08;3x3卷积、1x1卷积、恒等映射&#xff09;和推理时的单分支合并&#xff0c;在精度与速度间取得了优…

kotlin 知识点 七 泛型的高级特性

对泛型进行实化 泛型实化这个功能对于绝大多数Java 程序员来讲是非常陌生的&#xff0c;因为Java 中完全没有这个概 念。而如果我们想要深刻地理解泛型实化&#xff0c;就要先解释一下Java 的泛型擦除机制才行。 在JDK 1.5之前&#xff0c;Java 是没有泛型功能的&#xff0c;…

Transformer LLaMA

一、Transformer Transformer&#xff1a;一种基于自注意力机制的神经网络结构&#xff0c;通过并行计算和多层特征抽取&#xff0c;有效解决了长序列依赖问题&#xff0c;实现了在自然语言处理等领域的突破。 Transformer 架构摆脱了RNNs&#xff0c;完全依靠 Attention的优…

Qt学习 网络编程 TPC通信

一 基本网络端口 1 网络编程基本概念 通讯方式&#xff1a;信息的通讯时通过网络来进行&#xff0c;通讯方式有两种&#xff0c;TCP和UDP通信&#xff0c;TCP通讯是专用通道&#xff0c;指定某个信息只能走某个通道&#xff0c;UDP则是非专用通道&#xff0c;比如一个车队&am…

ESP32-S3 实战指南:BOOT-KEY 按键驱动开发全解析

一、基础知识 本篇我们使用 BOOT 按键来学习一下 GPIO 功能&#xff0c;首先补充一下相关术语介绍。 1、GPIO&#xff08;General Purpose Input/Output&#xff09; GPIO 是微控制器上的通用引脚&#xff0c;既可以作为输入&#xff08;读取外部信号&#xff09;&#xff0…

ssh配置 远程控制 远程协作 github本地配置

0.设备版本 windows11 ubuntu24.0.4 1.1 在 Linux 上启用 SSH 服务 首先&#xff0c;确保 Linux 计算机上安装并启用了 SSH 服务。 安装和启动 OpenSSH 服务&#xff08;如果未安装&#xff09; # 在终端安装 OpenSSH 服务&#xff08;如果尚未安装&#xff09; sudo apt …

C++知识整理day9——继承(基类与派生类之间的转换、派生类的默认成员函数、多继承问题)

文章目录 1.继承的概念和定义2.基类与派生类之间的转换3.继承中的作用域4.派生类的默认成员函数5.实现一个不能被继承的类6.继承与友元7.继承与静态成员8.多继承和菱形继承问题8.1 继承分类及菱形继承8.2 虚继承 1.继承的概念和定义 概念&#xff1a; 继承(inheritance)机制是⾯…

2024年国赛高教杯数学建模A题板凳龙闹元宵解题全过程文档及程序

2024年国赛高教杯数学建模 A题 板凳龙闹元宵 原题再现 “板凳龙”&#xff0c;又称“盘龙”&#xff0c;是浙闽地区的传统地方民俗文化活动。人们将少则几十条&#xff0c;多则上百条的板凳首尾相连&#xff0c;形成蜿蜒曲折的板凳龙。盘龙时&#xff0c;龙头在前领头&#x…

华为认证考试证书下载步骤(纸质+电子版)

华为考试证书可以通过官方渠道下载相应的电子证书&#xff0c;部分高级认证如HCIE还支持申请纸质证书。 一、华为电子版证书申请步骤如下&#xff1a; ①访问华为培训与认证网站 打开浏览器&#xff0c;登录华为培训与认证官方网站 ②登录个人账号 在网站首页&#xff0c;点…

【Android】用 chrome://inspect/#devices 调试H5页面

通常做Android开发的过程中&#xff0c;不可避免的需要遇到去与H5交互&#xff0c;甚至有时候需要去调试H5的信息。 这里分享一下Android工程里如何调试H5页面信息&#xff1a; 直接在浏览器地址栏输入 &#xff1a; chrome://inspect/#devices 直接连接手机usb,打开开发者模式…

贪心算法精品题

1.找钱问题 本题的贪心策略在于我们希望就可能的保留作用大的5元 class Solution { public:bool lemonadeChange(vector<int>& bills) {std::map<int ,int> _map;for(auto ch:bills){if(ch 5) _map[ch];else if(ch 10){if(_map[5] 0) return false;else{_m…

WEB1~6通杀

##解题思路 这六道题&#xff0c;通杀了&#xff0c;只因为是PHP的特性 来&#xff0c;看web6&#xff0c;过滤最复杂的正则&#xff0c;而且不能解析成大于999的值&#xff0c;但是&#xff0c;php是弱类型的语言&#xff0c;我只要输入任意字符数字&#xff0c;最终值就为0&…

孜然单授权系统V2.0PHP授权系统

孜然单授权V1.0系统&#xff0c;延续了2022年开发的孜然多应用授权系统V2.0 变更&#xff1a;多应用变单系统&#xff0c;去除没用的垃圾代码&#xff0c;从0开发&#xff0c;去除了一些没用的功能 完善了开发文档&#xff0c;之前那套是我写着玩的屎山代码&#xff0c;V1.0将展…

Apache SeaTunnel 构建实时数据同步管道(最新版)

文章作者 王海林 白鲸开源 数据集成引擎研发 Apache SeaTunnel Committer & PMC Member&#xff0c;Apache SkyWalking Committer&#xff0c;多年平台研发经验&#xff0c;目前专注于数据集成领域。 导读 在当今数字化快速发展的时代&#xff0c;数据已然成为企业决策…

服务器离线部署DeepSeek

目标 本次部署的目标是在本地服务器上部署DeepSeek。但是该服务不能连接外网&#xff0c;因此只能使用离线部署的方式。为了一次完成部署。现在云服务器上进行尝试。 云服务器部署尝试 云服务器配置 CentOS72080Ti 11GB 安装准备 1、上传iso并配置为本地yum源 安装前先将…

ComfyUI:Stable Diffusion 及 LoRA、VAE 、ControlNet模型解析

目录 Stable Diffusion流程 扩散过程 去噪过程 checkpoints LoRA LoRA 位置与结构 LoRA 层与原层的关系 LoRA 层的参数拆解 VAE 训练特定 VAE 时更新的参数部分 ControlNet ControlNet 位置与结构 ControlNet 的训练过程 ControlNet 的参数处理与信息融合 Contr…

微信小程序:多菜单栏设计效果

一、实现效果 二、代码 wxml 编辑前端界面,步骤 菜单逻辑: 逐步取出数组中的项,首先取出顶部菜单项,然后选中后取出选中的底部数据(左侧菜单+右侧内容),然后点击左侧菜单取出选中的左侧菜单对应的右侧内容 ①这里我的数据是全部封装到一个数组对象的,首先我的循环…

【Linux Oracle】time命令+oracle exp压缩

Linux && Oracle相关文档&#xff0c;希望互相学习&#xff0c;共同进步 风123456789&#xff5e;-CSDN博客 1.说明 Linux中的time命令&#xff1a;主要用于测量命令的执行时间&#xff0c;并显示该命令在执行过程中所使用的系统资源情况&#xff0c;如CPU时间、内存和…

20分钟 Bash 上手指南

文章目录 bash 概念与学习目的第一个 bash 脚本bash 语法变量的使用位置参数管道符号&#xff08;过滤条件&#xff09;重定向符号条件测试命令条件语句case 条件分支Arrayfor 循环函数exit 关键字 bash 脚本记录历史命令查询文件分发内容 bash 概念与学习目的 bash&#xff0…