如何利用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++實現mysql連接池內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!