如何用C++本身實現mysql數據庫的鏈接池?

爲何是mysql?

如今幾乎全部的後臺應用都要用到數據庫,什麼關係型的、非關係型的;正當關係的,不正當關係的;主流的和非主流的, 大到Oracle,小到sqlite,以及包括如今逐漸流行的基於物聯網的時序數據庫,好比濤思的TDengine,我們中國人本身的開源時序數據庫,性能槓槓滴。mysql

凡此總總,即便沒用過,也據說過,但大部分人或企業用的最多的就是白嫖型數據庫:mysql。該數據庫的特色就是不管是我的仍是企業都能玩的起。像Oracle這種名媛型數據庫基本就屬於銀行特供,銀行須要花錢買平安,內心踏實。不買對的,只選貴的,由於人家確實不差錢。sql

若是你的後臺應用連數據庫都不須要,那跟鹹魚網站有什麼區別呢?就是鹹魚二手網也要用到數據庫的。若是一個IT民工一生沒用過數據庫就在35(~45)歲時「被退休」,那他的職業生涯是遺憾的,是不完美的,是不純粹的。 好歹年輕是也要用一下非主流的Access吧,哪怕Execel也成。這種感受就比如在大學時沒談過戀愛同樣,光忙着羨慕別人就忽然畢業了。數據庫

爲何要搞資源池?

目前大部分後臺程序都選擇Java開發或PHP,這兩種語言的第三方庫很是豐富,豐富到讓開發人員的只要將精力放在具體業務上便可。好比數據庫的資源池,只要選擇好適當的jar包外加配置好相應的數據庫參數,便可放心大膽的使用mysql。windows

固然,若是你命硬的話,也能夠選擇用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");
}

輸出打印

相關文章
相關標籤/搜索