C++数据库连接池

文章目录


连接池

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

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

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。在并发程度比较高的时候,连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率。


C API

  • MYSQL *mysql_real_connect:连接mysql服务器。
  • int mysql_query(MYSQL *mysql, const char *query); 执行sql语句
  • MYSQL_RES *mysql_store_result(MYSQL *mysql); 获取结果集
  • unsigned int mysql_num_fields(MYSQL_RES *result) 得到结果集的列数
  • MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *result); 获取表头 -> 列名(字段名)
  • unsigned long *mysql_fetch_lengths(MYSQL_RES *result); 得到结果集中字段的长度
  • MYSQL_ROW mysql_fetch_row(MYSQL_RES *result); 遍历结果集
  • void mysql_free_result(MYSQL_RES *result); 释放结果集
  • void mysql_close(MYSQL *mysql); 关闭mysql实例
  • const char *mysql_character_set_name(MYSQL *mysql) 为当前连接返回默认的字符集。
  • int mysql_set_character_set(MYSQL *mysql, char *csname); 设置api使用的字符集
  • my_bool mysql_autocommit(MYSQL *mysql, my_bool mode) mysql中默认会进行事务的提交。
  • my_bool mysql_commit(MYSQL *mysql); 事务提交
  • my_bool mysql_rollback(MYSQL *mysql) 数据回滚
  • const char *mysql_error(MYSQL *mysql); 打印错误信息
  • unsigned int mysql_errno(MYSQL *mysql); 返回错误编号
  • Windows:libmysql.dll 库。
  • Linux:libmysqlclient.so

数据库步骤

数据库连接顺序

  1. 初始化连接环境
  2. 连接mysql的服务器,需要提供如下连接数据: IP,端口,用户名,密码,数据库名字
  3. 数据库的增删查改操作
  4. 事务处理:提交事务;或者回滚。
  5. 数据库的读操作
  6. 遍历结果集。
  7. 释放资源。

数据库连接池

  • 单例模式,只需要一个类对象。
  • 所有的数据库连接应该维护到一个安全的队列中,直接使用STL的queue。
  • 在需要的时候可以从连接池中得到一个或多个可用的数据库连接,对于连接的动态创建和销毁,用单独的线程去处理。
  • 如果队列中没有多余的可用连接,需要动态的创建新连接。
  • 如果队列中空闲的连接太多,需要动态的销毁一部分。
  • 数据库操作完毕,需要将连接归还到连接池中。生产者和消费者模型:锁,条件变量。

数据库连接服务操作模块:

cpp 复制代码
#pragma once
#include <iostream>
#include <mysql.h>
#include <chrono>
using namespace std;
using namespace chrono;
class MysqlConn
{
public:
    // 初始化数据库连接
    MysqlConn();
    // 释放数据库连接
    ~MysqlConn();
    // 连接数据库
    bool connect(string user, string passwd, string dbName, string ip, unsigned short port = 3306);
    // 更新数据库: insert, update, delete
    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 freeResult();
    MYSQL* m_conn = nullptr;
    MYSQL_RES* m_result = nullptr;
    MYSQL_ROW m_row = nullptr;
    steady_clock::time_point m_alivetime;// 绝对时钟判断连接存活时长
};
cpp 复制代码
#include "MysqlConn.h"

MysqlConn::MysqlConn()
{
    m_conn = mysql_init(nullptr); //初始化对象
    mysql_set_character_set(m_conn, "utf8"); // 设置utf-8编码格式
}

MysqlConn::~MysqlConn()
{
    if (m_conn != nullptr)
    {
        mysql_close(m_conn); //关闭连接
    }
    freeResult(); // 释放一个结果集合使用的内存。
}

bool MysqlConn::connect(string user, string passwd, string dbName, string ip, unsigned short port)
{
    MYSQL* ptr = mysql_real_connect(m_conn, ip.c_str(), user.c_str(), passwd.c_str(), dbName.c_str(), port, nullptr, 0); //连接mysql服务器
    return ptr != nullptr;
}

bool MysqlConn::update(string sql)
{
    if (mysql_query(m_conn, sql.c_str())) //更新数据库
    {
        return false;
    }
    return true;
}

bool MysqlConn::query(string sql)
{
    freeResult();
    if (mysql_query(m_conn, sql.c_str()))
    {
        return false;
    }
    m_result = mysql_store_result(m_conn); // 保存结果集
    return true;
}

bool MysqlConn::next()
{
    if (m_result != nullptr)
    {
        m_row = mysql_fetch_row(m_result);
        if (m_row != nullptr)
        {
            return true;
        }
    }
    return false;
}

string MysqlConn::value(int index)
{
    int rowCount = mysql_num_fields(m_result);
    if (index >= rowCount || index < 0)
    {
        return string();
    }
    char* val = m_row[index];
    unsigned long length = mysql_fetch_lengths(m_result)[index];
    return string(val, length);
}

bool MysqlConn::transaction()
{
    return mysql_autocommit(m_conn, false); //手动提交,不自动提交事务。
}

bool MysqlConn::commit()
{
    return mysql_commit(m_conn); //提交事务
}

bool MysqlConn::rollback()
{
    return mysql_rollback(m_conn); // 事务回滚
}

void MysqlConn::refreshAliveTime()
{
    m_alivetime = steady_clock::now();
}

long long MysqlConn::getAliveTime()
{
    nanoseconds res = steady_clock::now() - m_alivetime;// 纳秒 较高精度
    milliseconds millsec = duration_cast<milliseconds>(res);// 毫秒 精度相对较低
    return millsec.count();
}

void MysqlConn::freeResult()
{
    if (m_result)
    {
        mysql_free_result(m_result);
        m_result = nullptr;
    }
}

连接池

cpp 复制代码
#pragma once
#include <queue>
#include <mutex>
#include <condition_variable>
#include "MysqlConn.h"
using namespace std;
class ConnectionPool
{
public:
    static ConnectionPool* getConnectPool(); // 创建单例模式
    ConnectionPool(const ConnectionPool& obj) = delete; //禁止使用拷贝构造
    ConnectionPool& operator=(const ConnectionPool& obj) = delete; // 禁止使用拷贝赋值运算符
    shared_ptr<MysqlConn> getConnection();
    ~ConnectionPool();
private:
    ConnectionPool(); //构造函数设置为私有的,单例模式
    bool parseJsonFile();
    void produceConnection();
    void recycleConnection();
    void addConnection();

    string m_ip;// 数据库服务器ip地址
    string m_user;// 数据库服务器用户名
    string m_passwd;// 数据库服务器密码
    string m_dbName;// 数据库服务器的数据库名
    unsigned short m_port;// 数据库服务器绑定的端口
    int m_minSize;// 连接池维护的最小连接数
    int m_maxSize;// 连接池维护的最大连接数
    int m_timeout;// 连接池获取连接的超时时长
    int m_maxIdleTime;// 连接池中连接的最大空闲时长


    queue<MysqlConn*> m_connectionQ; // 连接队列
    mutex m_mutexQ; 
    condition_variable m_cond;
};
cpp 复制代码
#include "ConnectionPool.h"
#include <json/json.h>
#include <fstream>
#include <thread>
using namespace Json;
ConnectionPool* ConnectionPool::getConnectPool()
{
    static ConnectionPool pool;
    return &pool;
}

bool ConnectionPool::parseJsonFile()
{
    ifstream ifs("dbconf.json");
    Reader rd;
    Value root;
    rd.parse(ifs, root);
    if (root.isObject())
    {
        m_ip = root["ip"].asString();
        m_port = root["port"].asInt();
        m_user = root["userName"].asString();
        m_passwd = root["password"].asString();
        m_dbName = root["dbName"].asString();
        m_minSize = root["minSize"].asInt();
        m_maxSize = root["maxSize"].asInt();
        m_maxIdleTime = root["maxIdleTime"].asInt();
        m_timeout = root["timeout"].asInt();
        return true;
    }
    return false;
}

// 生产连接线程函数
void ConnectionPool::produceConnection()
{
    while (true)
    {
        unique_lock<mutex> locker(m_mutexQ); // 锁
        while (m_connectionQ.size() >= m_minSize)
        {
            m_cond.wait(locker); // 条件变量
        }
        addConnection(); // 执行连接
        m_cond.notify_all(); // 通知消费者线程消费
    }
}


// 销毁连接
void ConnectionPool::recycleConnection()
{
    while (true)
    {
        this_thread::sleep_for(chrono::milliseconds(500));
        lock_guard<mutex> locker(m_mutexQ);
        while (m_connectionQ.size() > m_minSize)
        {
            MysqlConn* conn = m_connectionQ.front();
            if (conn->getAliveTime() >= m_maxIdleTime) // 判断连接存活时长,如果满足条件就行删除
            {
                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();
    m_connectionQ.push(conn);
}

// 取出可用连接
shared_ptr<MysqlConn> ConnectionPool::getConnection()
{
    unique_lock<mutex> locker(m_mutexQ);
    while (m_connectionQ.empty()) // 判断连接是否为空
    {
        if (cv_status::timeout == m_cond.wait_for(locker, chrono::milliseconds(m_timeout)))// 阻塞一段时间
        {
            if (m_connectionQ.empty())
            {
                //return nullptr;
                continue;
            }
        }
    }
    // lambda表达式定义shared_ptr的销毁函数。
    shared_ptr<MysqlConn> connptr(m_connectionQ.front(), [this](MysqlConn* conn) {
        lock_guard<mutex> locker(m_mutexQ);
        conn->refreshAliveTime();
        m_connectionQ.push(conn);
        });
    m_connectionQ.pop();
    m_cond.notify_all();// 唤醒生产者线程,附带着也会唤醒消费者线程
    return connptr;
}

ConnectionPool::~ConnectionPool()
{
    while (!m_connectionQ.empty())
    {
        MysqlConn* conn = m_connectionQ.front();
        m_connectionQ.pop();
        delete conn;
    }
}

ConnectionPool::ConnectionPool()
{
    // 加载配置文件
    if (!parseJsonFile())
    {
        return;
    }

    for (int i = 0; i < m_minSize; ++i) // 默认情况保证有minsize个数的连接就行了。
    {
        addConnection();
    }
    thread producer(&ConnectionPool::produceConnection, this);// 生产连接
    thread recycler(&ConnectionPool::recycleConnection, this);// 看有没有需要销毁的连接
    producer.detach();
    recycler.detach();
}

参考列表:

https://www.bilibili.com/video/BV1Fr4y1s7w4

https://blog.csdn.net/CrankZ/article/details/82874158

相关推荐
南东山人2 小时前
一文说清:C和C++混合编程
c语言·c++
Ysjt | 深4 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__4 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
Microsoft Word5 小时前
c++基础语法
开发语言·c++·算法
一只小小汤圆5 小时前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
legend_jz5 小时前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE6 小时前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
ö Constancy6 小时前
c++ 笔记
开发语言·c++
fengbizhe7 小时前
笔试-笔记2
c++·笔记