提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
在进行数据库操作的时候为了提高数据库(关系型数据库)的访问瓶颈,除了在服务器端增加缓存服务器(例如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 封装设计
- 数据库连接的存储:可用使用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服务器,主要分为已经几个步骤:
- 初始化连接环境
- 连接mysql的服务器,需要提供如下连接数据:
- 服务器的IP地址 服务器监听的端口(默认端口是3306)
- 连接服务器使用的用户名(默认是 root),和这个用户对应的密码
- 要操作的数据库的名字
-
连接已经建立, 后续操作就是对数据库数据的添删查改
-
如果要进行数据 添加/ 删除/ 更新,需要进行事务的处理
- 需要对执行的结果进行判断
- 成功:提交事务
- 失败:数据回滚
- 需要对执行的结果进行判断
-
数据库的读操作 -> 查询 -> 得到结果集
-
遍历结果集 -> 得到了要查询的数据
-
释放资源
三、封装编码
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(