基於C++11的資料庫連線池實現

雲夢士發表於2022-05-24

0.注意

該篇文章為了讓大家儘快看到效果,程式碼放置比較靠前,看程式碼前務必看下第4部分的基礎知識。

1.資料庫連線池

1.1 是什麼?

資料庫連線池負責分配、管理和釋放資料庫連線,屬於池化機制的一種,類似的還有執行緒池等。

1.2 為什麼用?

各種池化技術的使用原因都是類似的,也就是單獨操作比較浪費系統資源,利用池提前準備一些資源,在需要時可以重複使用這些預先準備的資源,從而減少系統開銷,實現資源重複利用。對於資料庫連線來關閉來說,需要經過四步:
(1)建立通訊連線的 TCP 三次握手
(2)資料庫伺服器的連線認證
(3)資料庫伺服器關閉連線時的資源回收
(4)斷開通訊連線的 TCP 四次揮手
而利用資料庫連線池則減少了這幾步的系統開銷,更加的高效。

1.3如何設計?

原理類似於線性池,在資料庫連線池中提前建立好多個資料庫連線,使用時從資料庫連線池中取出,使用完放回資料庫連線池。資料庫連線池中的資料庫連線排程由資料庫連線池排程。

2.基於C++11的實現

Talk is cheap. Show me the code.

直接看程式,原理、函式在後面再介紹。

2.1程式

(0)全部程式碼:
下載之一即可
百度網盤連結:https://pan.baidu.com/s/1wvcLn0CgZxbDpYdapDVUow?pwd=bnd9 提取碼:bnd9
阿里雲盤連線:https://www.aliyundrive.com/s/Emsy9UJLxiv 提取碼: h46t
夸克網盤連結:https://pan.quark.cn/s/58eb69a3f0fb 提取碼:iRiw
(1)MysqlConn.h

#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();//釋放m_result空間
    MYSQL* m_conn = nullptr;
    MYSQL_RES* m_result = nullptr;
    MYSQL_ROW m_row = nullptr;
    steady_clock::time_point m_alivetime;//當前時間點
};

(2)MysqlConn.cpp

#include "MysqlConn.h"

MysqlConn::MysqlConn()
{
    m_conn = mysql_init(nullptr);//初始化mysql
    mysql_set_character_set(m_conn, "utf8");//設定編碼格式維utf8
}

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);
    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); //檢索結果集的下一行,如果沒有要檢索的行,mysql_fetch_row()返回NULL
        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];//為了避免下一步在“/0”處被截斷
    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;
    }
}

(3)ConnectionPool.h

#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;
    ~ConnectionPool();

    shared_ptr<MysqlConn> getConnection();//任務從連線池中獲取一個連線
    
private:
    ConnectionPool();//單例模式

    bool parseJsonFile();//解析Json配置檔案
    void produceConnection();//生產新的連線
    void recycleConnection();//回收多餘連線
    void addConnection();//新增單個連線

    //MysqlConn::connect所需要的引數
    string m_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;//條件變數
};

(4)ConnectionPool.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;
            }
        }
    }
    shared_ptr<MysqlConn> connptr(m_connectionQ.front(), [this](MysqlConn* conn) {
        lock_guard<mutex> locker(m_mutexQ);
        conn->refreshAliveTime();
        m_connectionQ.push(conn);
        });//自定義shared_ptr的析構方法
    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)
    {
        addConnection();
    }
    thread producer(&ConnectionPool::produceConnection, this);
    thread recycler(&ConnectionPool::recycleConnection, this);
    producer.detach();
    recycler.detach();
}

2.2 測試程式碼main.cpp

#include <iostream>
#include <memory>
#include "MysqlConn.h"
#include "ConnectionPool.h"
using namespace std;
// 1. 單執行緒: 使用/不使用連線池
// 2. 多執行緒: 使用/不使用連線池

void op1(int begin, int end)
{
    for (int i = begin; i < end; ++i)
    {
        MysqlConn conn;
        conn.connect("root", "123159", "testdb", "192.168.237.131");
        char sql[1024] = { 0 };
        sprintf(sql, "insert into person 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 person values(%d, 25, 'man', 'tom')", i);
        conn->update(sql);
    }
}

void test1()
{
#if 1
    // 非連線池, 單執行緒, 用時: 21037278300 納秒, 21037 毫秒
    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
    // 連線池, 單執行緒, 用時: 8838406500 納秒, 8838 毫秒
    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 test2()
{
#if 0
    // 非連線池, 多單執行緒, 用時: 13277417000 納秒, 13277 毫秒
    MysqlConn conn;
    conn.connect("root", "root", "testdb", "192.168.237.131");
    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
    // 連線池, 多單執行緒, 用時: 3938502100 納秒, 3938 毫秒
    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 query()
{
    MysqlConn conn;
    bool tt = conn.connect("root", "123159", "testdb", "127.0.0.1");
    cout << "tt:  " << tt << endl;
    string sql = "insert into person values(7, 25, 'man', 'tom')";
    bool flag = conn.update(sql);
    cout << "flag value:  " << flag << endl;

    sql = "select * from person";
    conn.query(sql);
    while (conn.next())
    {
        cout << conn.value(0) << ", "
            << conn.value(1) << ", "
            << conn.value(2) << ", "
            << conn.value(3) << endl;
    }
    return 0;
}
int main()
{
    test2();
    return 0;
}

3.配置

需要配置jsoncpp和mysql
(1)配置jasoncpp

(2)配置mysql

4.基礎知識

4.1MySQL在C語言中的API

參考:https://www.mysqlzh.com/doc/196/115.html

4.2 jsoncpp的基本知識

(1)基本函式

(2)解析Json格式資料

5.參考

https://www.bilibili.com/video/BV1Fr4y1s7w4
https://subingwen.cn/cpp/dbconnectionPool/

相關文章