您的位置:首页 > 汽车 > 时评 > 【C++】基于C++11的数据库连接池

【C++】基于C++11的数据库连接池

2024/12/25 0:59:22 来源:https://blog.csdn.net/qq_50921201/article/details/140131733  浏览:    关键词:【C++】基于C++11的数据库连接池

提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、连接池设计
    • 1.1 功能设计
    • 1.2 封装设计
  • 二、连接数据库步骤
  • 三、封装编码
    • 3.1 VS配置
      • 3.1.1 MySQL环境
      • 3.1.2 jsoncpp环境
    • 3.2 连接类代码设计
      • 3.2.1 连接类MysqlConn.h
      • 3.2.1 连接类MysqlConn.cpp
    • 3.3 连接池代码设计
      • 3.3.1 连接池connectionPool.h
      • 3.3.2 连接池connectionPool.cpp
    • 3.4 测试函数
  • 四、 总结
  • 问题总结
    • MySQL “error: ‘fd’: 未知重写说明符”
    • LNK2019 无法解析的外部符号 _mysql_set_character_set@8,
    • 由于找不到libmysql.dIl, 无法继续执行代码。重新安装程序可能会解决此问题


前言

在进行数据库操作的时候为了提高数据库(关系型数据库)的访问瓶颈,除了在服务器端增加缓存服务器(例如redis)缓存常用的数据之外,还可以增加连接池,来提高数据库服务器的访问效率。

一般来说,对于数据库操作都是在访问数据库的时候创建连接,访问完毕断开连接。但是如果在高并发情况下,有些需要频繁处理的操作就会消耗很多的资源和时间,比如:

  • 建立通信连接的TCP三次握手
  • 数据库服务器的连接认证
  • 数据库服务器关闭连接时的资源回收
  • 断开通信连接的TCP四次挥手

如果使用数据库连接池会减少这一部分的性能损耗

编写代码需要的头文件:
#include<mysql.h>
#include<json/json.h>
#include<jsoncpp.h>

API对应的MySQL动态库
Windows:libmysql.dll
Linux:libmysqlclient.os


一、连接池设计

1.1 功能设计

要设计一个数据库连接池,我们需要实现以下几个功能点:

  1. 连接池只需要一个实例,所以连接池类应该是一个单例模式的类 (此为设计模式)
  2. 所有的数据库连接应该维护到一个安全的队列中
    • 使用队列的目的是方便连接的添加和删除
    • 安全指的是线程安全,也就是说需要使用互斥锁来保护队列数据的读写。
  3. 在需要的时候可以从连接池中得到一个或多个可用的数据库连接
    • 如果有可用连接,直接取出
    • 如果没有可用连接,阻塞等待一定时长然后再重试
    • 如果队列中没有多余的可用连接,需要动态的创建新连接
    • 如果队列中空闲的连接太多,需要动态的销毁一部分
  4. 数据库操作完毕,需要将连接归还到连接池中
    在这里插入图片描述

1.2 封装设计

  • 数据库连接的存储:可用使用STL中的队列queue
  • 连接池连接的动态创建:这部分工作需要交给一个单独的线程来处理
  • 连接池连接的动态销毁:这部分工作需要交给一个单独的线程来处理
  • 数据库连接的添加和归还:这是一个典型的生产者和消费者模型
    • 消费者:需要访问数据库的线程,数据库连接被取出(消费)
    • 生产者:专门负责创建数据库连接的线程
    • 处理生产者和消费者模型需要使用条件变量阻塞线程
  • 连接池的默认连接数量:连接池中提供的可用连接的最小数量
    • 如果不够就动态创建
    • 如果太多就动态销毁
  • 连接池的最大连接数量:能够创建的最大有效数据库连接上限
  • 最大空闲时间:创建出的数据库连接在指定时间长度内一直未被使用,此时就需要销毁该连接。
  • 连接超时:消费者线程无法获取到可用连接是,阻塞等待的时间长度

综上所述,数据库连接池对应的单例模式的类的设计如下:

using namespace std;
/*
* 数据库连接池: 单例模式
* MySqlConn 是一个连接MySQL数据库的类
*/
class ConnectionPool {
public:// 得到单例对象static ConnectionPool* getConnectPool();// 从连接池中取出一个连接shared_ptr<MySqlConn> getConnection();// 删除拷贝构造和拷贝赋值运算符重载函数ConnectionPool(const ConnectionPool& obj) = delete;ConnectionPool& operator=(const ConnectionPool& obj) = delete;private:// 构造函数私有化ConnectionPool();bool parseJsonFile();void produceConnection();void recycleConnection();void addConnection();string m_ip;             // 数据库服务器ip地址string m_user;           // 数据库服务器用户名string m_dbName;         // 数据库服务器的数据库名string m_passwd;         // 数据库服务器密码unsigned short m_port;   // 数据库服务器绑定的端口int m_minSize;           // 连接池维护的最小连接数int m_maxSize;           // 连接池维护的最大连接数int m_maxIdleTime;       // 连接池中连接的最大空闲时长int m_timeout;           // 连接池获取连接的超时时长queue<MySqlConn*> m_connectionQ; // 连接队列mutex m_mutexQ;  // 互斥锁condition_variable m_cond; // 条件变量
};

二、连接数据库步骤

在程序中连接MySql服务器,主要分为已经几个步骤:

  1. 初始化连接环境
  2. 连接mysql的服务器,需要提供如下连接数据:
  • 服务器的IP地址 服务器监听的端口(默认端口是3306)
  • 连接服务器使用的用户名(默认是 root),和这个用户对应的密码
  • 要操作的数据库的名字
  1. 连接已经建立, 后续操作就是对数据库数据的添删查改

  2. 如果要进行数据 添加/ 删除/ 更新,需要进行事务的处理

    • 需要对执行的结果进行判断
      • 成功:提交事务
      • 失败:数据回滚
  3. 数据库的读操作 -> 查询 -> 得到结果集

  4. 遍历结果集 -> 得到了要查询的数据

  5. 释放资源

三、封装编码

3.1 VS配置

3.1.1 MySQL环境

打开项目的属性窗口,在属性页配置MySQL头文件目录和MySQL的库目录
在这里插入图片描述
将下载的mysql路径下的include和lib文件目录分别写入包含目录和库目录中
在这里插入图片描述
在VS项目的属性页窗口指定要加载的动态库对应的导入库(xxx.lib)
在这里插入图片描述
我们在调用MySQL API的使用需要加载的动态库为libmysql.dll,它对应的导入库为libmysql.lib,在该窗口的附加依赖项位置指定的就是这个导入库的名字。

3.1.2 jsoncpp环境

参考博客:jsoncpp的编译和使用

3.2 连接类代码设计

3.2.1 连接类MysqlConn.h

#pragma once
#include <string>
#include <WinSock2.h>//必须要有,解决“fd”:未知重写说明符
#include <algorithm>
#include<chrono>
#include<mysql.h>
using namespace std;
using namespace chrono;
class MysqlConn
{
public:// 初始化数据库连接MysqlConn();// 释放数据库连接~MysqlConn();// 连接数据库bool connect(string userName,string passwd,string dbName,string ip,unsigned short port=3306);// 更新数据库bool update(string sql);// 查询数据库bool query(string sql);// 遍历查询得到的结果集bool next();// 得到结果集中的字段值string value(int index);// 事务操作bool transaction();// 提交事务bool commit();// 事务回滚bool rollback();// 刷新起始的空闲时间点void refreshAliveTime();// 计算连接存活的时长long long getAliveTime();private:void freeResource();  // 释放资源MYSQL* m_conn = nullptr; // 保存初始化数据库时返回的地址MYSQL_RES* m_result = nullptr; // 保存查询结果MYSQL_ROW m_row = nullptr; // 保存当前记录中每个字段的值steady_clock::time_point m_alivetime; // 存活时间
};

3.2.1 连接类MysqlConn.cpp

#include "MysqlConn.h"// 初始化数据库连接
MysqlConn::MysqlConn()
{// 初始化数据库this->m_conn = mysql_init(nullptr);// 设置接口的字符编码,防止在数据库操作时中文乱码mysql_set_character_set(this->m_conn, "utf8");
}// 释放数据库连接
MysqlConn::~MysqlConn()
{// 释放内存if (this->m_conn != nullptr) {mysql_close(this->m_conn);}this->freeResource();
}// 连接数据库
bool MysqlConn::connect(string userName, string passwd, string dbName, string ip, unsigned short port)
{// c_str()函数返回一个指向正规C字符串的指针常量, 内容与本string串相同。// 因为调用的MysqlAPI是C语言规范MYSQL* ptr = mysql_real_connect(this->m_conn, ip.c_str(), userName.c_str(), passwd.c_str(), dbName.c_str(), port, nullptr, 0);return ptr!=nullptr;
}// 更新数据库
bool MysqlConn::update(string sql)
{// 执行失败返回非0,执行成功返回0if (mysql_query(this->m_conn,sql.c_str())){return false;}return true;
}// 查询数据库
bool MysqlConn::query(string sql)
{// 先清空上次查询的数据结果this->freeResource();if (mysql_query(this->m_conn, sql.c_str())){return false;}// 保存查询结果// mysql_store_result()将查询的全部结果读取到客户端,分配1个MYSQL_RES结构,并将结果置于该结构中。this->m_result = mysql_store_result(this->m_conn);return false;
}// 遍历查询得到的结果集
bool MysqlConn::next()
{if (this->m_result!=nullptr){// 会返回MYSQL_ROW类型的地址,是一个二级指针,指向一个指针数组,指针数组中的元素指向值this->m_row = mysql_fetch_row(this->m_result);}return false;
}// 得到结果集中的字段值
string MysqlConn::value(int index)
{// 得到查询结果的列数int columnCount = mysql_num_fields(this->m_result);// 如果查询的索引越界if (index >= columnCount || index < 0){return string();}// 获取想要查询的值char* val = m_row[index];/*return string(val);*/  // 但是查询的结果中可能存在'\0'这样的字符,此时string()就会出问题// 所有需要得到当前值的长度unsigned long len = mysql_fetch_lengths(this->m_result)[index];return string(val, len);
}// 事务操作
bool MysqlConn::transaction()
{// 第二个参数设置为false,为手动提交return mysql_autocommit(this->m_conn,false);
}// 提交事务
bool MysqlConn::commit()
{return mysql_commit(this->m_conn);
}// 事务回滚
bool MysqlConn::rollback()
{return mysql_rollback(m_conn);
}// 刷新起始的空闲时间点
void MysqlConn::refreshAliveTime()
{// 得到当前的时间戳this->m_alivetime = steady_clock::now();
}// 计算连接存活的时长
long long MysqlConn::getAliveTime()
{nanoseconds res = steady_clock::now() - this->m_alivetime;milliseconds millisec = duration_cast<milliseconds>(res); // 类型转换,将低精度往高精度转换return millisec.count(); // 计算millisec中有几个毫秒
}// 释放资源
void MysqlConn::freeResource()
{if (this->m_result){mysql_free_result(this->m_result);this->m_result = nullptr;}
}

3.3 连接池代码设计

3.3.1 连接池connectionPool.h

#pragma once
#include<memory>
#include<queue>
#include<mutex>
#include<chrono>
#include"MysqlConn.h"
using namespace std;
using namespace chrono;
/*
* 数据库连接池: 单例模式
* MySqlConn 是一个连接MySQL数据库的类
*/
class connectionPool {
public:// 通过静态方法得到单例对象static connectionPool* getConnectPool();// 从连接池中取出一个连接shared_ptr<MysqlConn> getConnection();// 析构函数~connectionPool();// 删除拷贝构造和拷贝赋值运算符重载函数,防止类的复制,实现单例模式connectionPool(const connectionPool& obj) = delete;connectionPool& operator=(const connectionPool& obj) = delete;private:// 构造函数私有化,防止外界可以实例化对象,打破单例模式connectionPool();// 解析json文件的函数bool parseJsonFile();// 生产数据库连接的函数void produceConnection();// 销毁数据库连接的函数void recycleConnection();// 创建新的连接对象void addConnection();string m_ip;             // 数据库服务器ip地址string m_user;           // 数据库服务器用户名string m_dbName;         // 数据库服务器的数据库名string m_passwd;         // 数据库服务器密码unsigned short m_port;   // 数据库服务器绑定的端口int m_minSize;           // 连接池维护的最小连接数int m_maxSize;           // 连接池维护的最大连接数int m_maxIdleTime;       // 连接池中连接的最大空闲时长int m_timeout;           // 连接池获取连接的超时时长queue<MysqlConn*> m_connectionQ; // 连接队列,连接数据库时可以在此进行连接mutex m_mutexQ;  // 互斥锁condition_variable m_cond; // 条件变量};

3.3.2 连接池connectionPool.cpp

#include "connectionPool.h"
#include<fstream>
#include<json/json.h>
#include<thread>
#include<iostream>
using namespace Json;// 通过静态方法得到单例对象,饿汉模式
connectionPool* connectionPool::getConnectPool()
{static connectionPool pool;return &pool;
}// 进行连接
shared_ptr<MysqlConn> connectionPool::getConnection()
{unique_lock<mutex> locker(this->m_mutexQ);// 判断连接队列是否为空while (this->m_connectionQ.empty()){// wait_for() 当时间片转完还是没有被唤醒,则会返回timeout状态// if判断语句为真,则说明连接队列为空if (cv_status::timeout == this->m_cond.wait_for(locker, chrono::milliseconds(this->m_timeout)))// 为空就阻塞进程一定时间{if (this->m_connectionQ.empty()) // 保险起见又做一次判断{continue;}}}// 利用智能指针取出连接对象,智能指针可以直接指定删除器进行连接对象回收shared_ptr<MysqlConn> connptr(this->m_connectionQ.front(), [this](MysqlConn* conn) { // 指定匿名删除器// lock_guard在加锁后,可以自动在生命周期结束时对互斥锁进行解锁,一般是在函数结束时lock_guard<mutex> locker(this->m_mutexQ); // 重新指定被放回队列的时间conn->refreshAliveTime();// 放回连接队列this->m_connectionQ.push(conn);});// 弹出被取出的连接对象this->m_connectionQ.pop();// 唤醒生产者线程this->m_cond.notify_all();return connptr;
}connectionPool::~connectionPool()
{while (!this->m_connectionQ.empty()){MysqlConn* conn = this->m_connectionQ.front();this->m_connectionQ.pop();delete conn;conn = nullptr;}
}// 解析json文件的函数
bool connectionPool::parseJsonFile()
{ifstream ifs("dbconn.config.json");Reader rd;Value root;rd.parse(ifs, root); // 将ifs流对象中的json数据解析到root中// 判断转换的是否是json数据,成功转化后,将其存储到成员变量中,并返回trueif (root.isObject()){this->m_ip = root["ip"].asString();this->m_port = root["port"].asInt();this->m_user = root["userName"].asString();this->m_passwd = root["password"].asString();this->m_dbName = root["dbName"].asString();this->m_minSize = root["minsize"].asInt();this->m_maxSize = root["maxsize"].asInt();this->m_maxIdleTime = root["maxsize"].asInt();this->m_timeout = root["timeout"].asInt();return true;}return false;
}// 当连接队列中连接对象太少时,需要创建一部分连接对象
void connectionPool::produceConnection()
{while (true){// unique_lock是个类模板//mutex是参数的类型,unique_lock包装了一个互斥锁对象,当函数结束时,此unique_lock析构,进行解锁unique_lock<mutex> locker(this->m_mutexQ);while (this->m_connectionQ.size() >= this->m_minSize) // 当连接池中的连接数量大于最小数量时,就阻塞不需要生产连接 {// 阻塞进程,不生产连接m_cond.wait(locker);}// 生产连接this->addConnection();// 唤醒因没有连接对象而阻塞的线程this->m_cond.notify_all();}
}// 当连接队列中连接对象太多时,需要销毁一部分连接对象释放资源
void connectionPool::recycleConnection()
{while (true){// 设置为每隔1检测一次this_thread::sleep_for(chrono::seconds(1));lock_guard<mutex> locker(this->m_mutexQ);// 当连接队列元素个数大于最小连接数时while (this->m_connectionQ.size() >= this->m_minSize){// 检测连接对象的空闲时长,这个是从未使用开始计算的(即从被连接池中返还的连接队列时)MysqlConn* conn = m_connectionQ.front();if (conn->getAliveTime() >= this->m_maxIdleTime) // 检测到队头连接对象存活时间大于了最大空闲时间{this->m_connectionQ.pop();delete conn;}else{break;}}}
}// 创建新的连接对象,并添加到连接队列中
void connectionPool::addConnection()
{MysqlConn* conn = new MysqlConn;conn->connect(m_user, m_passwd, m_dbName, m_ip, m_port);conn->refreshAliveTime();this->m_connectionQ.push(conn);
}// 实现构造函数
connectionPool::connectionPool() 
{// 加载配置文件if (!this->parseJsonFile()){// 未加载成功return;}int num = 0;// 初始化最小连接数的连接对象for (int i = 0; i < this->m_minSize; i++){this->addConnection();cout << ++num << endl;}// 将生产/销毁连接交给子线程去做// 这里将子线程的任务函数设置为类的非静态成员函数thread producer(&connectionPool::produceConnection,this);thread recycler(&connectionPool::recycleConnection,this);// 将主线程和子线程分离,避免阻塞主线程producer.detach();recycler.detach();
}

3.4 测试函数

#include<iostream>
#include<string>
#include"MysqlConn.h"
#include"connectionPool.h"
using namespace std;// @file:connectionPool
// @author:IdealSanX_T
// @date:2024/7/3 9:56:19
// @brief:连接线程池测试函数// 单线程不使用线程池
void op1(int begin, int end)
{for (int i = begin; i < end; ++i){MysqlConn conn;conn.connect("root", "123456", "504", "127.0.0.1");char sql[1024] = { 0 };sprintf(sql, "insert into test values (%d,25,' man', 'tom')",i);conn.update(sql);}
}// 单线程使用线程池
void op2(connectionPool* pool,int begin, int end)
{for (int i = begin; i < end; ++i){shared_ptr<MysqlConn> conn = pool->getConnection();char sql[1024] = { 0 };sprintf(sql, "insert into test values (%d,25,' man', 'tom')", i);conn->update(sql);cout << "插入数据" << endl;}
}// 单线程测试函数
void test01()
{
#if 0// 非连接池,单线程,用时:4592767400纳秒,4592毫秒steady_clock::time_point begin = steady_clock::now(); op1(0, 5000);steady_clock::time_point end = steady_clock:: now();auto length = end - begin; cout << "非连接池,单线程,用时:" << length.count() << "纳秒,"<< length.count() / 1000000 << "毫秒" << endl;
#else// 连接池,单线程,用时:2199883200纳秒,2199毫秒connectionPool* pool = connectionPool::getConnectPool();steady_clock::time_point begin = steady_clock::now();op2(pool, 0, 5000);steady_clock::time_point end = steady_clock::now();auto length = end - begin;cout << "连接池,单线程,用时:" << length.count() << "纳秒,"<< length.count() / 1000000 << "毫秒" << endl;
#endif
}// 多线程测试函数
void test02()
{
#if 0// 非连接池,多单线程,用时:2791891000纳秒,2791毫秒MysqlConn conn;conn.connect("root", "123456", "504", "127.0.0.1");steady_clock::time_point begin = steady_clock::now();thread t1(op1, 0, 1000);thread t2(op1, 1000, 2000); thread t3(op1, 2000, 3000); thread t4(op1, 3000, 4000); thread t5(op1, 4000, 5000); t1.join();t2.join(); t3.join(); t4.join(); t5.join();steady_clock::time_point end = steady_clock::now(); auto length = end - begin;cout << "非连接池,多单线程,用时:" << length.count()<<"纳秒,"<< length.count() / 1000000 << "毫秒" <<endl;#else// 连接池,多单线程,用时:1974331000纳秒,1974毫秒connectionPool* pool = connectionPool::getConnectPool();steady_clock::time_point begin = steady_clock::now(); thread t1(op2, pool, 0, 1000);thread t2(op2, pool, 1000, 2000); thread t3(op2, pool, 2000, 3000); thread t4(op2, pool, 3000, 4000); thread t5(op2, pool, 4000, 5000); t1.join();t2.join(); t3.join(); t4.join(); t5.join();steady_clock::time_point end = steady_clock::now(); auto length = end - begin;cout << "连接池,多单线程,用时:" << length.count() <<"纳秒,"<< length.count() / 1000000 << "毫秒" << endl;
#endif
}
int main() {//test01();test02();system("pause");return 0;
}

四、 总结

使用连接池时,比不使用连接池所用时更短,效果更好。
可以在消费者/生产者线程中进行代码优化。

问题总结

MySQL “error: ‘fd’: 未知重写说明符”

增加引用头文件
当用C/C++ 连接数据库并且采用ODBC(Open DataBases Connection) 肯定会出现
#include 这个头文件,关键就是这个头文件的问题,

在预编译 #include <mysql.h> 一定要先包含 #include <winsock.h> 这个头文件才不会出现刚才的问题。

winsock.h这个头文件一定要在mysql.h的头文件前面

原因:#include "mysql.h"中调用了mysql_com.h,而mysql_com.h使用了有关网络套接字的fd,所以如果没有网络通信的头文件的话,就会报错。

#include <WinSock2.h>//必须要有,解决“fd”:未知重写说明符

借鉴博客

LNK2019 无法解析的外部符号 _mysql_set_character_set@8,

原因如下,系统是win10x64,MySQL 64位的lib也是64位的接口。所以解决方法如下:
.项目->属性->配置管理器
活动解决方案平台,下拉选新建,出现一个新的对号框,在键入选择新平台中选择X64
然后把VS配置的include目录和lib目录等再次配置一下。
重新编译 成功~~~

由于找不到libmysql.dIl, 无法继续执行代码。重新安装程序可能会解决此问题

解决方法:
1.首先找到 libmysql.dll 文件。(在MySQL解压后的文件夹中的lib文件夹目录下)
2.然后直接将“libmysql.dll”复制到项目同级目录下,问题解决。
( 我的项目名为SQLConnectionPool,我就把文件复制到SQLConnectionPool目录下,如图所示)
在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com