如何用C++自己实现mysql数据库的连接池?

Posted 一只会铲史的猫

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何用C++自己实现mysql数据库的连接池?相关的知识,希望对你有一定的参考价值。

为什么是mysql

现在几乎所有的后台应用都要用到数据库,什么关系型的、非关系型的;正当关系的,不正当关系的;主流的和非主流的, 大到Oracle,小到sqlite,以及包括现在逐渐流行的基于物联网的时序数据库,比如涛思的 TDengine,咱们中国人自己的开源时序数据库,性能杠杠滴。

凡此总总,即使没用过,也听说过,但大部分人或企业用的最多的就是白嫖型数据库:mysql。该数据库的特点就是无论是个人还是企业都能玩的起。像Oracle这种名媛型数据库基本就属于银行特供,银行需要花钱买平安,心里踏实。不买对的,只选贵的,因为人家确实不差钱。

如果你的后台应用连数据库都不需要,那跟咸鱼网站有什么区别呢?就是咸鱼二手网也要用到数据库的。如果一个IT民工一辈子没用过数据库就在35(~45)岁时“被退休”,那他的职业生涯是遗憾的,是不完美的,是不纯粹的。 好歹年轻是也要用一下非主流的Access吧,哪怕Execel也成。这种感觉就好比在大学时没谈过恋爱一样,光忙着羡慕别人就突然毕业了。

为什么要搞资源池?

目前大部分后台程序都选择Java开发或php,这两种语言的第三方库非常丰富,丰富到让开发人员的只要将精力放在具体业务上即可。比如数据库的资源池,只要选择好适当的jar包外加配置好相应的数据库参数,即可放心大胆的使用mysql。

当然,如果你命硬的话,也可以选择用C或C++开发后台应用。这时候你就需要自己DIY一个数据库资源池。

如果只是一个客户端程序,基本不需要连接池,但对于后台应用来说,高并发就意味着多线程,多线程程就意味着资源的竞争。内存访问如此,数据库访问也是如此。每次数据库的打开和关闭就是一次网络连接和关闭的过程,频繁的打开和关闭无疑会浪费大量的系统资源。这时候就需要提前建立好N个连接,并放在资源池中并提供给不同线程访问使用。

mysql资源池实现的案例源码

我一直相信好的代码是不需要过的语言来解释的,代码即文档,要啥自行车。以下案例只是一个实现思路,供参考。

头文件:MysqlPool.h


#pragma warning(disable : 4786) 

#include <windows.h>
#include <winsock2.h>
#include <mysql.h>                  // 确保你的机器有mysql开发库
#include <vector>
#include <string>
using namespace std;

#define DEFAULT_POOL_SIZE       20  // 缺省mysql连接池中的数量
#define DEFAULT_POOL_TIMEOUT    60  // 获取池中mysql连接的超时

// 自定义数据库查询回调函数
typedef BOOL (CALLBACK *LPFN_RetrieveRecordData)(MYSQL_ROW& sqlRow, MYSQL_FIELD* pSqlFields, int iFieldCount, DWORD dwUserData);

// Mysql数据库连接类
class CMysqlConn
{
public:
    CMysqlConn(const char* pszDBServer, UINT uDBPort, const char* pszDBName, 
                const char* pszDBUser, const char* pszDBPwd);
    virtual ~CMysqlConn();

public:
    // 打开/关闭一个mysql连接
    BOOL Open();
    void Close();

    // ping连接是否已关闭
    BOOL Ping();
    // 重置字符集
    BOOL ResetCharset();

public:
    // ================SQL语句操作(简单实现几个)================
    // 查询
    BOOL    Select(const char* pszSql, LPFN_RetrieveRecordData lpfnRetrieveRecordData, DWORD dwUserData);
    // 执行
    BOOL    Execute(const char* pszSql);
    // 插入,如果主键是自增整型,返回插入后的主键值
    __int64 Insert(const char* pszSql);

protected:
    MYSQL*  m_pMysql;           // mysql数据库操作对象

    // 以下是连接mysql需要的参数
    string  m_strDBServer;      // mysql数据库所在服务器
    UINT    m_uDBPort;          // mysql数据库连接端口
    string  m_strDBName;        // 数据库名称
    string  m_strDBUser;        // 数据库账户
    string  m_strDBPwd;         // 数据库密码

};

// 数据库连接池实现
class CMysqlPool  
{
public:
    CMysqlPool();
    virtual ~CMysqlPool();

    // 创建mysql连接池
    BOOL Create(const char* pszDBServer, UINT uDBPort, const char* pszDBName, 
                const char* pszDBUser, const char* pszDBPwd,
                DWORD dwPoolSize = DEFAULT_POOL_SIZE, 
                DWORD dwTimeOut = DEFAULT_POOL_TIMEOUT);            
    // 销毁连接池
    void Destroy();

public:

    // 获取一个mysql连接
    CMysqlConn* Get();

    // 释放一个mysql连接
    void Release(CMysqlConn* pConn);

protected:
    HANDLE              m_hSemaphore;       // 信号量句柄
    DWORD               m_dwPoolSize;       // 连接池大小 
    DWORD               m_dwTimeOut;        // 超时,单位秒
    CRITICAL_SECTION    m_csPool;           // 连接池锁

    vector<CMysqlConn*> m_vecIdle;          // 闲队列
    vector<CMysqlConn*> m_vecBusy;          // 忙队列
};

实现文件:MysqlPool.cpp


#include "stdafx.h"
#include "MysqlPool.h"
#include <assert.h>
#include <algorithm>

#pragma comment(lib, "libmysql.lib")    //连接MysQL需要的库

//////////////////////////////////////////////////////////////////////
// CMysqlConn: mysql数据库连接类
//////////////////////////////////////////////////////////////////////

CMysqlConn::CMysqlConn(const char* pszDBServer, UINT uDBPort, const char* pszDBName, 
                        const char* pszDBUser, const char* pszDBPwd)
{
    assert(pszDBServer);
    assert(pszDBName);
    assert(pszDBUser);
    assert(pszDBPwd);

    m_pMysql = NULL;
    m_strDBServer = pszDBServer;
    m_uDBPort = uDBPort;
    m_strDBName = pszDBName;
    m_strDBUser = pszDBUser;
    m_strDBPwd = pszDBPwd;
}

CMysqlConn::~CMysqlConn()
{
    Close();
}

// 打开一个mysql数据库,即建立一个数据库连接
BOOL CMysqlConn::Open()
{
    if(m_pMysql)
    {
        mysql_close(m_pMysql);  // 关闭连接 
        m_pMysql = NULL;
    }

    m_pMysql = mysql_init(NULL);
    if(!m_pMysql)
        return FALSE;

    // 连接数据库
    if(!mysql_real_connect(m_pMysql, m_strDBServer.c_str(), m_strDBUser.c_str(),
                            m_strDBPwd.c_str(), m_strDBName.c_str(), m_uDBPort, NULL, 0))
    {
        int i = mysql_errno(m_pMysql);
        const char * pszErr = mysql_error(m_pMysql);

        return FALSE;
    }

    // 设置重连
    char chValue = 1;
    mysql_options(m_pMysql, MYSQL_OPT_RECONNECT, &chValue); 
    mysql_query(m_pMysql,"set names \'gbk\'"); 

    return TRUE;
}

// 关闭数据库连接
void CMysqlConn::Close()
{
    if(m_pMysql)
        mysql_close(m_pMysql);  // 断开连接
    m_pMysql = NULL;    
}

// ping一下mysql,看看连接还活着
BOOL CMysqlConn::Ping()
{
    if(m_pMysql)
        return (0 == mysql_ping(m_pMysql));
    return FALSE;
}

// 设置字符集为GBK
BOOL CMysqlConn::ResetCharset()
{
    if(m_pMysql)
        return (0 == mysql_query(m_pMysql, "set names \'gbk\'")); 
    return FALSE;
}

// mysql执行:delete 或 update
BOOL CMysqlConn::Execute(const char* pszSql)
{
    assert(pszSql);

    if(!m_pMysql)
        return FALSE;

    MYSQL_STMT *myStmt = mysql_stmt_init(m_pMysql);
    if(!myStmt)
    {
        return FALSE;
    }

    if(0 != mysql_stmt_prepare(myStmt, pszSql, strlen(pszSql)))
    {
        mysql_stmt_close(myStmt);
        return FALSE;
    }
    if(0 != mysql_stmt_execute(myStmt))
    {
        mysql_stmt_close(myStmt);
        return FALSE;
    }
    mysql_stmt_close(myStmt);

    return TRUE;        
}

// mysql插入
__int64 CMysqlConn::Insert(const char* pszSql)
{   
    assert(pszSql);

    MYSQL_STMT *myStmt = mysql_stmt_init(m_pMysql);
    if(!myStmt)
        return 0;

    if(0 != mysql_stmt_prepare(myStmt, pszSql, strlen(pszSql)))
    {
        int i = mysql_errno(m_pMysql);
        const char * s = mysql_error(m_pMysql);
        mysql_stmt_close(myStmt);
        return 0;
    }
    if(0 != mysql_stmt_execute(myStmt))
    {
        mysql_stmt_close(myStmt);
        return 0;
    }
    mysql_stmt_close(myStmt);

    __int64 i64ID = mysql_insert_id(m_pMysql);  
    return i64ID;
}

// mysql查询
BOOL CMysqlConn::Select(const char* pszSql, LPFN_RetrieveRecordData lpfnRetrieveRecordData, DWORD dwUserData)
{
    if(!m_pMysql)
        return FALSE;

    if(NULL == lpfnRetrieveRecordData)
        return FALSE;

    if(0 != mysql_real_query(m_pMysql, pszSql, strlen(pszSql)))
    {
        return FALSE;   
    }

    MYSQL_RES *resRecord = mysql_store_result(m_pMysql);
    int iFieldCount = resRecord->field_count;

    MYSQL_ROW sqlRow;
    while (sqlRow = mysql_fetch_row(resRecord))
    {
        if(!lpfnRetrieveRecordData(sqlRow, resRecord->fields, iFieldCount, dwUserData))
            break;
    }
    mysql_free_result(resRecord);
    return TRUE;
}

//////////////////////////////////////////////////////////////////////
// CMysqlPool: mysql数据库连接池类
//////////////////////////////////////////////////////////////////////

CMysqlPool::CMysqlPool()
{
    ::InitializeCriticalSection(&m_csPool);
}

CMysqlPool::~CMysqlPool()
{
    Destroy();
    ::DeleteCriticalSection(&m_csPool);
}

// 创建mysql连接池
BOOL CMysqlPool::Create(const char* pszDBServer, UINT uDBPort, const char* pszDBName, 
                        const char* pszDBUser, const char* pszDBPwd,
                        DWORD dwPoolSize, DWORD dwTimeOut)
{
    m_dwTimeOut = dwTimeOut;
    m_dwPoolSize = dwPoolSize;

    // 创建信号量
    m_hSemaphore = ::CreateSemaphore(NULL, dwPoolSize, dwPoolSize, NULL);
    if (NULL == m_hSemaphore)
    {
        return FALSE;
    }

    // 创建数据库连接池
    for(DWORD i = 0; i < dwPoolSize; ++i)
    {
        // 创建一个mysql数据库连接
        CMysqlConn *pConn = new CMysqlConn(pszDBServer, uDBPort, pszDBName, pszDBUser, pszDBPwd);
        if(!pConn->Open())  
        {
            delete pConn;
            continue;
        }
        m_vecIdle.push_back(pConn);
    }

    return m_vecIdle.size() > 0;

}

// 销毁mysql连接池
void CMysqlPool::Destroy()
{
    ::CloseHandle(m_hSemaphore);
    m_hSemaphore = NULL;

    // 释放idle队列
    vector<CMysqlConn*>::iterator it;
    for(it = m_vecIdle.begin(); it != m_vecIdle.end(); ++it)
    {
        CMysqlConn* pConn =  *it;
        delete pConn;
    }
    m_vecIdle.clear();

    // 释放busy队列
    while(!m_vecBusy.empty())
    {
        CMysqlConn* pConn =  m_vecBusy.back();
        m_vecBusy.pop_back();
        delete pConn;
    }   
}

// 从mysql连接池获取一个连接
CMysqlConn* CMysqlPool::Get()
{
    DWORD dwRet = ::WaitForSingleObject(m_hSemaphore, m_dwTimeOut*1000);

    if (WAIT_OBJECT_0 != dwRet)             // 超时,说明资源池没有可用mysql连接
    {
        printf("数据库没有可用连接。\\r\\n");
        return NULL;
    }

    // 从连接池中获取一个闲置连接
    CMysqlConn* pConn = NULL;

    ::EnterCriticalSection(&m_csPool);

    if (!m_vecIdle.empty())
    {
        pConn = m_vecIdle.back();           // 移出idle队列
        m_vecIdle.pop_back();   
        m_vecBusy.push_back(pConn);         // 加入busy队列
    }
    ::LeaveCriticalSection(&m_csPool);

    if(NULL == pConn)
        return NULL;

    // 如果一个连接长时间无通信,可能被防火墙关闭,此时可以通过mysql_ping函数测试一下
    // 本例中通过重新设置字符集
    // 重新设置字符集,并判断数据库连接是否已断开
    if(!pConn->ResetCharset())          
    {
        if(!pConn->Open())
            return NULL;
    }

    printf("==》资源池:记得还我哦。\\r\\n");
    return pConn;
}

// 释放一个连接到mysql连接池
void CMysqlPool::Release(CMysqlConn* pConn)
{
    if(NULL == pConn)
        return;

    // 释放一个信号量
    ::ReleaseSemaphore(m_hSemaphore, 1, NULL); 

    ::EnterCriticalSection(&m_csPool);

    // 从Busy队列中释放该连接
    vector<CMysqlConn*>::iterator it = find(m_vecBusy.begin(), m_vecBusy.end(), pConn);
    if(it != m_vecBusy.end())
    {
        printf("POOL SIZE : %d, %d\\r\\n", m_vecIdle.size(), m_vecBusy.size());
        m_vecBusy.erase(it);                // 移出busy队列
        m_vecIdle.push_back(pConn);         // 加入idle队列
        printf("POOL SIZE : %d, %d\\r\\n", m_vecIdle.size(), m_vecBusy.size());
    }
    ::LeaveCriticalSection(&m_csPool);

    printf("《==资源池说:有借有还再借不难,常来玩啊。\\r\\n");
}

测试函数

void TestMysqlPool()
{
    // 创建mysql连接资源池
    CMysqlPool mysqlPool;
    if(!mysqlPool.Create("127.0.0.1", 3306, "information_schema", "root", "123456"))
    {
        printf("Create mysql conneticon pool failed.\\r\\n");
        return;
    }

    // 从资源池中获取一个连接,连接池说:记得要还哦!
    CMysqlConn* pConn = mysqlPool.Get();

    // 假装做一次数据库操作
    char* pszSQL =  "SELECT * FROM CHARACTER_SETS";
    pConn->Select(pszSQL, RetrieveRecordData, 0);

    // 将连接还给资源池并谢谢!连接池说:不客气!
    mysqlPool.Release(pConn);

    printf("Test over.\\r\\n");
}

输出打印

以上是关于如何用C++自己实现mysql数据库的连接池?的主要内容,如果未能解决你的问题,请参考以下文章

如何用C++实现支持HTTPS的RESTful WebServer

如何用VC++ 连接 Mysql数据库

如何用java实现mysql数据库的导入导出

如何用python创建数据库

如何用mysql实现ODBC(JDBC桥)连接数据库?

如何用Eclipse连接MySQL数据库