如何利用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"); }
输出打印