数据库连接池(C++11实现)

news2024/12/26 0:21:46

目的:

        因为对数据库的操作实质上是对磁盘的IO操作,所以如果对数据库访问次数过多,就会到导致大量的磁盘IO,为了提高MySQL数据库(基于C/S设计)的访问瓶颈,除了在服务器端增加缓存服务器缓存常用的数据 之外(例如Redis),还可以增加连接池,来提高MySQL Server的访问效率。

        在高并发情况下,大量的 TCP三次握手、MySQL Server连接认证、MySQL Server关闭连接回收资源和TCP四次挥手所耗费的时间也十分明显,增加连接池就是为了减少这一部分的性能损耗。

        在系统启动时就创建一定数量的连接,用户一旦执行CURD操作,直接拿出一条连接即可,不需要TCP的连接过程和资源回收过程,使用完该连接后归还给连接池的连接队列,供之后使用。

功能介绍:

 1、初始连接数(initSize):
        初始连接量表示连接池事先会和MySQL Server创建的initSize数量的SqlConn连接。在完成初始连接量之后,当应用发起MySQL访问时,不用创建新的MySQLServer连接,而是从连接池中直接获取一个连接,当使用完成后,再把连接归还到连接池中。
2、最大容量(maxCapacity)
        连接池的最大容量即连接池内的最大连接数。当并发访问MySQL Server的请求增多时,初始连接量不够使用时,会根据新的请求数量去创建更多的连接给应用去使用,但是新创建的连接数量上限是maxCapacity,不能无限制的创建连接,因为每一个连接都会占用一个socket资源,一般连接池和服务器程序是部署在一台 主机上的,如果连接池占用过多的socket资源,那么服务器就不能接收太多的客户端请求了。当这些连接使用完成后,会再次归还到连接池当中来维护。
3、最大空闲时间(maxIdleTime)
        当高并发过去,因为高并发而新创建的连接在很长时间(maxIdleTime)内没有得到使用,那么这些新创建的连接处于空闲状态,并且占用着一定的资源,这个时候就需要将其释放掉,最终只用保存initSize个连接就行。
4、连接超时时间(connTimeOut)
        当MySQL的并发访问请求量过大,连接池中的连接数量已经达到了maxSize,并且此时连接池中没有可以使用的连接,那么此时应阻塞connTimeOut的时间,如果此时间内有使用完的连接归还到连接池,那么就可以使用,如果超过这个时间还是没有连接,那么获取数据库连接就失败,无法访问数据库。

运行流程:

1、使用单例模式(局部静态变量方法)获取连接池实例。

SqlConnPool* pool = SqlConnPool::getInstance();

2、从SqlConnPool中获取和Mysql的连接SqlConn,为了实现RAII的目标,将getConn()函数的返回值设置为shared_ptr<SqlConn>类型。

shared_ptr<SqlConn> sp = pool->getConn();

3、利用获取的连接,对数据库进行CURD操作。

sp->update(sql);
sp->query(sql);

SqlConn类结构         SqlConn.h

#ifndef SQLCONN_H
#define SQLCONN_H

#include <mysql/mysql.h>
#include <chrono>
#include <string>

class SqlConn
{
public:
	SqlConn();
	~SqlConn();
	//建立Sql连接
	bool connect(const std::string& ip,
				 const uint16_t port, 
				 const std::string& user, 
				 const std::string& pwd,
                 const std::string& dbName);

	// 更新操作 insert、delete、update
	bool update(const std::string& sql);
	// 查询操作 select
	MYSQL_RES* query(const std::string& sql);
    //刷新存活时间
	void refreshAliveTime()
	{
		aliveTime = std::chrono::steady_clock::now();
	}
    //获取存活时间间隔
	long long getAliveTime()
	{
		return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - aliveTime).count();
	}
private:
	MYSQL* conn;//数据库连接
	std::chrono::time_point<std::chrono::steady_clock> aliveTime; //连接存活时间
};

#endif // !SQLCONN_H

Sqlconn实现        Sqlconn.cpp

#include "sqlconn.h"

SqlConn::SqlConn()
{
    conn=mysql_init(nullptr);
    mysql_set_character_set(conn, "utf8");
}

SqlConn::~SqlConn()
{
    if(conn!=nullptr)
    {
        mysql_close(conn);
    }
}

bool SqlConn::connect(const std::string& ip,const uint16_t port,const std::string& user,const std::string& pwd,const std::string& dbName)
{
    MYSQL* ptr=mysql_real_connect(conn,ip.c_str(),user.c_str(),pwd.c_str(),dbName.c_str(),port,nullptr,0);
    return ptr!=nullptr;
}

bool SqlConn::update(const std::string& sql)
{
    if(mysql_query(conn,sql.c_str()))
    {
        return false;       
    }
    return true;
}

MYSQL_RES* SqlConn::query(const std::string& sql) 
{
    if(mysql_query(conn, sql.c_str())) 
    {
        return nullptr;
    }
    return mysql_use_result(conn);
}

注:mysql_query()函数有两种返回值,如果执行的是更新操作(insert、delete、update),则返回 true/ false;如果执行的是查询操作(select),则返回 MYSQL_RES* 类型。
 

SqlConnPool类结构         SqlConnPool.h

#ifndef SQLCONNPOOL_H
#define SQLCONNPOOL_H

#include <mysql/mysql.h>
#include <memory>
#include <string>
#include "sqlconn.h"
#include "blockqueue.h"
#include "log.h"

class SqlConnPool
{
public:
    static SqlConnPool* getInstance(int maxCapacity=1024)
    {
        static SqlConnPool single(maxCapacity);
        return &single;
    }
    std::shared_ptr<SqlConn> getConn();
    
private:
    SqlConnPool(int maxCapacity);
    ~SqlConnPool(){};
    SqlConnPool(const SqlConnPool& other)=delete;
    SqlConnPool& operator =(const SqlConnPool& other)=delete;
    //加载配置文件
    bool loadConfigFile();
    //专门用于产生新连接的线程
    void produceConnTask();
    //扫描超过最大空闲时间maxIdleTime的连接,对其进行回收
    void scannerConnTask();
    //添加新连接
    void addConn();

    std::string ip;
    uint16_t port;
    std::string user;
    std::string pwd;
    std::string dbName;

    size_t initSize;//初始连接数
    size_t maxIdleTime;//最大空闲时间
    size_t connTimeout;//超时时间

    BlockQueue<SqlConn*> connQue;
};

#endif // !SQLCONNPOOL_H

SqlConnPool实现        SqlConnPool.cpp

#include "sqlconnpool.h"
#include <functional>
using std::string;

bool SqlConnPool::loadConfigFile()//加载mysql.ini配置文件
{
    FILE* fp = fopen("./mysql.ini", "r");
    if (fp == nullptr)
    {
        LOG_ERROR("%s","mysql.ini file dose not exsit!");
        return false;
    }    
    while(!feof(fp)) //循环读取配置文件的每一行
    {
    	char line[1024] = {0};
        fgets(line, 1024, fp);
        string str = line;
        int idx = str.find('=', 0);
        if (idx == -1)
        {
            continue;
        }
        int endidx = str.find('\n', idx);
        string key = str.substr(0, idx);//截取key
        string value = str.substr(idx+1,endidx-idx-1);//截取value

        if (key == "ip")    
            ip = value;
        else if (key == "port")
            port = stoi(value);
        else if (key == "username")
            user = value;
        else if (key == "password") 
            pwd = value;
        else if (key == "dbName")
            dbName = value;
        else if (key == "initSize")
            initSize = stoi(value);
        else if (key == "maxIdleTime")
			maxIdleTime = stoi(value);
		else if (key == "connTimeOut")
			connTimeout = stoi(value);
    }
    return true;
}

 void SqlConnPool::addConn()//添加Sql连接
 {
    SqlConn* conn=new SqlConn();
    conn->connect(ip,port,user,pwd,dbName);
    conn->refreshAliveTime();
    connQue.push_back(conn);
 }

//构造SqlConnPool,初始化connQue变量
SqlConnPool::SqlConnPool(int maxCapacity):connQue(maxCapacity)
{
    if (!loadConfigFile())//配置失败
    {
        return ;
    }
    for (size_t i = 0; i < initSize; i++)//添加初始initSize数量的连接
    {
        addConn();
    }
    //生产者线程
    std::thread produce(std::bind(&SqlConnPool::produceConnTask, this));
    produce.detach();
    //扫描线程
    std::thread scanner(std::bind(&SqlConnPool::scannerConnTask, this));
    scanner.detach();
}

void SqlConnPool::produceConnTask()
{
    for(;;)
    {
        addConn();
    }
}

void SqlConnPool::scannerConnTask()
{
    for (;;)
    {
        std::this_thread::sleep_for(std::chrono::microseconds(maxIdleTime));
        while (connQue.get_size() > initSize)//清除超出最大空闲时间的连接
        {
            SqlConn* p = connQue.front();
            if(p->getAliveTime() >= maxIdleTime)
            {
                SqlConn* tmp=nullptr;
                connQue.pop(tmp);
                delete tmp;
            }
            else 
                break;
        }
    }
}

std::shared_ptr<SqlConn> SqlConnPool::getConn()//获取一个Sql连接
{
    SqlConn* conn;
    if (!connQue.pop(conn,connTimeout))//超过连接超时时间
    {
        LOG_ERROR("%s","get connection timeout");
        return nullptr;
    }
   std::shared_ptr<SqlConn> sp(conn,
          [&](SqlConn* p) 
          {
              p->refreshAliveTime();
              connQue.push_back(p);
          });//自定义删除函数(删除时将连接再次放入阻塞队列中,重复使用)
    return sp;
}

测试程序        testSqlConnPool.cpp

四种试验:多线程无连接池、多线程有连接池、单线程无连接池、单线程有连接池

分别测试其“建立10000个数据库连接,每个连接执行一次插入语句”的运行时间。

#include "sqlconn.h"
#include "sqlconnpool.h"
#include <time.h>
#include <iostream>
using namespace std;

void SigleWithConnection()//单线程有连接池
{
	time_t begin = clock();
	for (int i = 0; i < 10000; ++i)
	{
		SqlConnPool* pool = SqlConnPool::getInstance();

		shared_ptr<SqlConn> sp = pool->getConn();
		char sql[1024] = {0};
		sprintf(sql, "insert into user(username,password) values('%s','%s')","zhangsan","123456");
		sp->update(sql);
	}
	time_t end = clock();
	cout << (end - begin)/1000 << " ms" << endl;
}

void SigleNoConnection()//单线程无连接池
{
	time_t begin = clock();
	for (int i = 0; i < 10000; ++i)
	{
		SqlConn conn;
		char sql[1024] = {0};
		sprintf(sql, "insert into user(username,password) values('%s','%s')","zhangsan","123456");
		conn.connect("127.0.0.1", 3306, "root", "123456", "yourdb");
		conn.update(sql);
	}
	time_t end = clock();
	cout << (end - begin)/1000 << " ms" << endl;
}


void runInThreadNoConn()
{   
    for (int i = 0; i < 2500; ++i)
		{
			SqlConn conn;
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(username,password) values('%s','%s')","zhangsan","123456");
			conn.connect("127.0.0.1", 3306, "root", "123456", "yourdb");
			conn.update(sql);
		}
}

void MutiNoConnection()//多线程无连接池
{
	SqlConn conn;
	conn.connect("127.0.0.1", 3306, "root", "123456", "yourdb");
	time_t begin = clock();
	for(int i=0;i<4;i++)
    {
        thread t=thread(runInThreadNoConn);
        t.join();     
    }
	time_t end = clock();
	cout << (end - begin)/1000 << " ms" << endl;
}

void runInThreadByConn()
{   
    for (int i = 0; i < 2500; i++)
	{
		SqlConnPool* pool = SqlConnPool::getInstance();
        shared_ptr<SqlConn> sp = pool->getConn();
		char sql[1024] = {0};
		sprintf(sql, "insert into user(username,password) values('%s','%s')","zhangsan","123456");
		if (sp == nullptr)
		{
			cout << "sp is empty" << endl;
			continue;
		}
		sp->update(sql);
	}
}

void MutiWithConnection()//多线程有连接池
{
	time_t begin = clock();
	for(int i=0;i<4;i++)
    {
        thread t=thread(runInThreadByConn);
        t.join();     
    }
	time_t end = clock();
	cout << (end - begin)/1000 << " ms" << endl;
}

int main()
{ 
	MutiNoConnection();//多线程无连接池
	MutiWithConnection();//多线程有连接池
	SigleNoConnection();//单线程无连接池
	SigleWithConnection();//单线程有连接池
	return 0;
}

测试结果

 由结果可知,使用多线程+连接池的组合有效提高了数据库的访问效率。

  • 数据量10000,单线程从5280ms变成2080ms
  • 数据量10000,多线程从2140ms变成了1910ms

附:

配置文件        mysql.ini

# 数据库连接池配置文件
ip=127.0.0.1
port=3306
username=root
password=123456
dbName=yourdb
#初始连接数
initSize=4
# 最大空闲时间,单位ms
maxIdleTime=6000
# 连接超时时间,单位ms
connTimeOut=100

Makefile

CXX = g++
CFLAGS = -std=c++14 -O2 -Wall -g 

TARGET = testSqlConnPool
OBJS =  buffer.cpp log.cpp blockqueue.h\
		sqlconn.cpp sqlconnpool.cpp\
        testSqlConnPool.cpp

all: $(OBJS)
	$(CXX) $(CFLAGS) $(OBJS) -o $(TARGET)  -pthread -L/usr/lib64/mysql -lmysqlclient

clean:
	rm -rf $(OBJS) $(TARGET)

阻塞队列实现

基于C++11实现的阻塞队列(BlockQueue)_{(sunburst)}的博客-CSDN博客

删去代码中的LOG_XXXX语句,就无需链接 log.cpp,且无需链接仅与日志系统有关的 buffer.cpp 

日志系统实现

同步+异步日志系统(C++实现)_{(sunburst)}的博客-CSDN博客

缓冲区实现

缓冲区Buffer类的设计(参考Muduo实现)_{(sunburst)}的博客-CSDN博客

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

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

相关文章

还在用BERT做文本分类?分享一套基于预训练模型ERNIR3.0的文本多分类全流程实例【文本分类】

目录&#x1f340;一、前言&#x1f331;二、多分类场景简介&#x1f343;三、前期准备阶段&#x1f7e5;3.1 运行环境准备&#x1f7e7;3.2 文心ERNIE系列模型介绍&#x1f7e8;3.3 预训练模型加载⬜3.4 加载项目代码&#x1f490;四、数据准备阶段&#x1f7e9;4.1 数据处理流…

变不可能为可能——记房产推销员佟鑫海

有勤奋&#xff0c;就会有所收获。傲人的成绩和背后的努力密切相关。俗话说得好&#xff0c;没卖不掉的房子&#xff0c;仅有卖不掉房子的艺人经纪人。关键是你是否有恒心。 在明升&#xff0c;总会有这样一群影子&#xff0c;他们每天精力旺盛&#xff0c;衣着光鲜&#xff0…

【C/C++ SOCKET编程】基于TCP协议实现服务器客户端的简单通信

什么是SOCKET Socket又称"套接字"&#xff0c;应用程序通常通过"套接字"向网络发出请求或者应答网络请求&#xff0c;使主机间或者一台计算机上的进程间可以通讯。 TCP/IP协议 从字面意义上讲&#xff0c;有人可能会认为 TCP/IP 是指 TCP 和 IP 两种协议…

unsafe.Pointer和uintptr的区别

unsafe 包 func Alignof(x ArbitraryType) uintptr func Offsetof(x ArbitraryType) uintptr func Sizeof(x ArbitraryType) uintptr type ArbitraryType int type Pointer *ArbitraryType在unsafe包中&#xff0c;只提供了3个函数&#xff0c;两个类型。就这么少的量&#xf…

【数据结构进阶】布隆(Bloom Filter)过滤器【哈希+位图的整合】

布隆(Bloom Filter)过滤器【哈希位图的整合】 1、什么是布隆过滤器&#xff1f; 布隆过滤器&#xff08;Bloom Filter&#xff09;是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空…

物联网与射频识别技术,课程实验(四)

实验4—— 基于帧的时隙ALOHA(FSA)算法的实现与性能分析 实验说明&#xff1a; 利用Python或Matlab模拟基于帧的时隙ALOHA算法&#xff1b; 分析标签数量k、帧中所含时隙个数n对信道利用率的影响&#xff0c;其中&#xff0c; 信道利用率发送数据的时间/(发送数据的时间信道空…

【JavaEE】线程的状态转换

新年快乐! 祝新的一年万事胜意! 魅力无限! 随心所欲! 蒸蒸日上! 文章目录1. 线程的基本状态2.Java中线程的状态3. 线程的转换1. 线程的基本状态 操作系统中线程有三个基本状态,就绪状态,运行状态,阻塞状态. 就绪状态, 已经获得除CPU之外的所有资源,只要得到CPU,可立即执行. …

(二十五)大白话数据库无法连接故障的定位,Too many connections

文章目录 1、你是否遇到过Too many connections?2、linux的文件句柄数量被限制1、你是否遇到过Too many connections? 今天要给大家分析另外一个真实的大家都经常会碰到的数据库生产故障,就是数据库无法连接的问题。 大家会看到的异常信息往往是“ERROR 1040(HY000): Too …

ubuntu18.04下mysql数据库安装和C语言连接操作

数据库在应用系统开发中很常见&#xff0c;在众多的数据库中&#xff0c;mysql总是会占有一席之地。本篇说明一下如何在ubuntu18.04上安装mysql数据库。 目录 1.更新环境 2.安装mysql数据库系统 3.检测是否安装成功 4.启动、重启、关闭&#xff0c;删除 5.给root用户设置…

vue3+Ts使用vuex模块化和非模块化管理的2种方式(非pinia)

官网写的很清楚&#xff1a;https://vuex.vuejs.org/zh/guide/typescript-support.html 2种方式 (都不是使用pinia的) 1&#xff1a;复杂版本(不定义自己的 useStore 组合式函数) 使用的时候需要在vuex引入 useStore 在store文件引入导出的key import { useStore } from ‘vu…

CSS3新增的has伪类选择器,让你能轻松选择父元素

文章目录一、语法二、链式操作三、兼容性问题CSS现在新增了一个允许我们选择父元素的伪类&#xff1a;has选择器。可以将其当做父级选择器。 一、语法 选择器1:has(选择器2){} /* 表示选择包含有选择器2的所有的选择器1 比如&#xff1a;*/ div:has(p) {background: black; }…

计算机网络期末考试重点归纳

第 1 章 概述 1. 网络的基本特点 连通性共享性 2. internet 和 Internet 的含义 internetInternet中文名称互连网互联网/因特网名词性质通用名词专用名词名词解释指由多个计算机网络互连而成的计算机网络指当前全球最大的、开放的、由众多网络连接而成的特定互连网&#xff…

电子学会2020年6月青少年软件编程(图形化)等级考试试卷(一级)答案解析

目录 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 二、判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 三、编程题&#xff08;共3题&#xff0c;每题10分&#xff0c;共30分&#xff09; 青少年软件编程…

Ubuntu22.04下安装MongoDB(6.0版本)并进行相关数据库操作

前言 昨天用ubuntu22.04安装redis-5.0.5服务&#xff0c;因为版本不兼容&#xff0c;导致问题频发&#xff0c;最终在老师帮助下解决了&#xff0c;这又一次提醒了版本兼容的重要性 MongoDB安装与部署 因为所用ubuntu版本为22.04&#xff0c;所以不能按照老师所给文档进行安…

图书管理系统(Java实现简易版)

目录前言预期效果分析1. book包1.1 Book类1.2 BookList 类2. user包2.1 User类2.2 AdminUser类2.3 NormalUser类3. opera包3.1 IOPeration 接口3.2 AddOperation 类3.3 BrrowOperation 类3.4 DelOperation 类3.5 ExitOperation 类3.6 FindOperation 类3.7 ReturnOperation 类3.…

wikijs-一款wiki系统

2022&#xff0c;别了。 1、介绍 wikijs是一款知识共享wiki&#xff0c;优点是有权限管理系统、支持多人协作共同维护、支持markdown格式、支持评论、风格简洁等等。适合作为个人博客&#xff0c;或者小团队的文档知识库。 效果图&#xff1a; 2、部署流程 2.1、安装dock…

前端 | 装饰你的github profile(github 首页)

1.创建存储库 您可以创建一个与您的 github 帐户名同名的存储库 添加README文件 2.编辑README.md 现在&#xff0c;可以根据自己的喜好修改 repo 中的自述文件&#xff0c;但我在考虑包含哪些信息时查看了其他开发人员的资料。通常包括简短的介绍、使用的技术堆栈和联系方式…

【JAVA进阶】包装类,Arrays类,Lambda表达式

&#x1f4c3;个人主页&#xff1a;个人主页 &#x1f525;系列专栏&#xff1a;JAVASE基础 目录 一、包装类 二、Arrays类 三、Lambda表达式 一、包装类 其实就是8种基本数据类型对应的引用类型。 基本数据类型 引用数据类型 byte Byte short Short int Integer l…

GPU存储器架构

上表表述了各种存储器的各种特性。作用范围栏定义了程序的哪个部分能使用该存储器。而生存期定义了该存储器中的数据对程序可见的时间。除此之外&#xff0c;Ll和L2缓存也可以用于GPU程序以便更快地访问存储器。 总之&#xff0c;所有线程都有一个寄存器堆&#xff0c;它是最快…

Vue 页面渲染的流程

前言 在 Vue 核心中除了响应式原理外&#xff0c;视图渲染也是重中之重。我们都知道每次更新数据&#xff0c;都会走视图渲染的逻辑&#xff0c;而这当中牵扯的逻辑也是十分繁琐。 本文主要解析的是初始化视图渲染流程&#xff0c;你将会了解到从挂载组件开始&#xff0c;Vue…