引言:
数据库连接池和线程池的思想一样,是为了避免频繁创建和销毁数据库连接导致的性能开销。如果一个项目频繁的需要访问数据库,那么它就有可能需要频繁的创建/销毁数据库连接,那么我们可以采用数据库连接池的技术,在需要时,从数据库连接池中获取数据库连接,在用完数据库连接后再将它重新放回连接池中.
本文章所有代码大都来自开源项目:TinyWebServer
目录
- 设计模式:单例模式
- 数据库连接池
- 头文件与基本函数
- 连接池初始化
- 获取数据库连接
- 释放当前使用的连接
- 销毁整个连接池
- 源代码
设计模式:单例模式
单例模式就是指一个类有且只能有一个实例
实现单例模式的方法:
1.定义一个静态成员变量,用来保存唯一的一个实例
2.定义一个静态成员函数,用来让其他对象可以获取到这个实例
3.将构造函数设置为私有,防止其他类进行实例化对象
单例模式的好处:
1.避免多次实例化造成的性能和资源的开销
2.共享资源,由于只有一个实例,多个对象可以共享这个实例,实现资源共享和协作
小白对单例模式的误区:
单例模式是指该类只有一个实例,这个实例是保存在类中的静态成员变量中的,因为static关键字,所以所有的对象可以共享这个实例,并通过静态成员函数获取这个实例;但是这并不代表,单例模式只能创建一个对象,单例模式是可以创建多个对象的,然后这些对象通过静态成员函数获取实例将实例赋值给自己,实现相应的操作和共享数据等功能.
一个简单的单例模式的代码:
#include <iostream>
class Singleton {
private:
static Singleton* instance; // 单例实例
// 私有构造函数,防止外部实例化
Singleton() {}
public:
// 获取单例实例的静态方法
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
// 示例方法
void showMessage() {
std::cout << "Hello, I am a Singleton instance." << std::endl;
}
};
// 初始化静态成员变量
Singleton* Singleton::instance = nullptr;
int main() {
Singleton* singleton1 = Singleton::getInstance();
singleton1->showMessage();
Singleton* singleton2 = Singleton::getInstance();
singleton2->showMessage();
// 判断两个实例是否相等
if (singleton1 == singleton2) {
std::cout << "Both instances are the same." << std::endl;
} else {
std::cout << "Instances are different." << std::endl;
}
return 0;
}
数据库连接池
头文件与基本函数
使用数据库所需要的头文件:
#include <mysql/mysql.h>
接下来将介绍几个跟数据库有关的函数:
1.mysql_init
int mysql_init(MYSQL *mysql)
用于初始化一个mysql对象
2.mysql_real_connect
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag)
建立与mysql服务器的连接
参数1: MYSQL对象指针,用于接收连接句柄。
参数2: MySQL服务器主机名或IP地址。
参数3: MySQL用户名。
参数4: MySQL密码。
参数5: MySQL数据库名。
参数6: MySQL服务器端口号。
参数7: 额外的连接选项,可以为NULL。
3.mysql_close()
void mysql_close(MYSQL *mysql)
关闭与Mysql服务器的连接.
先只介绍这三个函数,因为我们的连接池只用这三个函数就可以实现,如果对其他函数感兴趣,可以去查找
头文件 <mysql/mysql.h>下的常用函数
TinyWebServer中
sql_connection_pool.h文件中的定义:
下面我会根据
TinywebServer项目中的
sql_connection_pool.cpp文件中对这些函数的具体实现做出分析来讲解数据库连接池
连接池初始化
void connection_pool::init(string url, string User, string PassWord, string DBName, int Port, int MaxConn, int close_log)
{
m_url = url;
m_Port = Port;
m_User = User;
m_PassWord = PassWord;
m_DatabaseName = DBName;
m_close_log = close_log;
for (int i = 0; i < MaxConn; i++)
{
MYSQL *con = NULL;
con = mysql_init(con);//初始化一个mysql结构体对象!
if (con == NULL)
{
LOG_ERROR("MySQL Error");//日志记录错误!
exit(1);
}
con = mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0);
if (con == NULL)
{
LOG_ERROR("MySQL Error");
exit(1);
}
connList.push_back(con);//将这个链接放入连接池队列中!
++m_FreeConn;//空闲的链接数+1;
}
reserve = sem(m_FreeConn); //信号量 sem=sem(
m_MaxConn = m_FreeConn;//最多连接数=当前所有空闲的连接数!
}
1.首先初始化自己的URL,端口,数据库用户名,数据库密码,数据库名,还有日志名等属性
2.然后根据最大的连接数,按照循环的方式,创建数据库连接,然后把数据库连接放入线程池,并更新信息:
2.1:先用mysql_init函数初始化一个mysql对象
2.2:用mysql_real_connect将mysql对象和数据库连接
2.3:将这个mysql对象添加到连接池队列中
2.4:更新基本信息:空闲的连接数+1
3.初始化一个信号量,信号量的初始值为数据库连接池中的连接数
4.更新基本信息,数据库连接池上限=当前空闲的连接数.
获取数据库连接
MYSQL *connection_pool::GetConnection()
{
MYSQL *con = NULL;
if (0 == connList.size())
return NULL;
reserve.wait();
lock.lock();
con = connList.front();
connList.pop_front();
--m_FreeConn;
++m_CurConn;
lock.unlock();
return con;
}
获取数据库连接就是从连接池中取出一个连接然后使用,步骤如下:
1.首先创建一个MYSQL对象指针,并设置为空
2.判断连接池中是否有连接,如果没有,那就拿取失败
3.信号量wait操作,让信号量的值-1,意味着我要开始拿资源了,如果信号量的值为0的话,此线程会被阻塞住,从而无法继续申请互斥锁拿资源.
4.加上互斥锁,防止多个线程同时拿取资源导致错误
5.从连接池中取出第一个连接
6.更改基本信息,空闲的连接-1,当前使用的连接数+1
7.释放互斥锁,让其他线程也能申请互斥锁拿资源
释放当前使用的连接
bool connection_pool::ReleaseConnection(MYSQL *con)
{
if (NULL == con)
return false;
lock.lock();
connList.push_back(con);
++m_FreeConn;
--m_CurConn;
lock.unlock();
reserve.post();
return true;
}
使用完一个数据库连接之后,应该再把这个连接放回连接池,步骤如下:
1.判断所要销毁的连接是否为空,如果为空,则直接退出
2.申请互斥锁,避免有其他的线程也进行释放操作
3.将这个连接放回连接池
4.更新基本信息:空闲连接+1,当前使用连接-1
5.将信号量的值+1,唤醒之前因为信号量所阻塞的线程,让他们可以继续申请互斥锁并获取连接.
销毁整个连接池
void connection_pool::DestroyPool()
{
lock.lock();
if (connList.size() > 0)
{
list<MYSQL *>::iterator it;
for (it = connList.begin(); it != connList.end(); ++it)
{
MYSQL *con = *it;
mysql_close(con);
}
m_CurConn = 0;
m_FreeConn = 0;
connList.clear();
}
lock.unlock();
}
将整个数据库连接池销毁,步骤如下:
1.加上互斥锁,避免多线程同时执行销毁操作
2.遍历整个连接池,使用mysql_close函数关闭连接池中的所有连接
3.更新基本信息:空闲连接=0,当前连接=0
4.清空整个连接池List
5.释放互斥锁
源代码
sql_connection_pool.cpp
#include <mysql/mysql.h>
#include <stdio.h>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <list>
#include <pthread.h>
#include <iostream>
#include "sql_connection_pool.h"
using namespace std;
connection_pool::connection_pool()
{
m_CurConn = 0;
m_FreeConn = 0;
}
connection_pool *connection_pool::GetInstance()
{
static connection_pool connPool;
return &connPool;
}
//构造初始化
void connection_pool::init(string url, string User, string PassWord, string DBName, int Port, int MaxConn, int close_log)
{
m_url = url;
m_Port = Port;
m_User = User;
m_PassWord = PassWord;
m_DatabaseName = DBName;
m_close_log = close_log;
for (int i = 0; i < MaxConn; i++)
{
MYSQL *con = NULL;
con = mysql_init(con);//初始化一个mysql结构体对象!
if (con == NULL)
{
LOG_ERROR("MySQL Error");//日志记录错误!
exit(1);
}
con = mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0);
//mysql.h中用于建立实际数据库连接的函数。它的原型声明如下:
//
if (con == NULL)
{
LOG_ERROR("MySQL Error");
exit(1);
}
connList.push_back(con);//将这个链接放入连接池队列中!
++m_FreeConn;//空闲的链接数+1;
}
reserve = sem(m_FreeConn); //信号量 sem=sem(
m_MaxConn = m_FreeConn;//最多连接数=当前所有空闲的连接数!
}
//当有请求时,从数据库连接池中返回一个可用连接,更新使用和空闲连接数
MYSQL *connection_pool::GetConnection()
{
MYSQL *con = NULL;
if (0 == connList.size())
return NULL;
reserve.wait();
lock.lock();
con = connList.front();
connList.pop_front();
--m_FreeConn;
++m_CurConn;
lock.unlock();
return con;
}
//释放当前使用的连接
bool connection_pool::ReleaseConnection(MYSQL *con)
{
if (NULL == con)
return false;
lock.lock();
connList.push_back(con);
++m_FreeConn;
--m_CurConn;
lock.unlock();
reserve.post();
return true;
}
//销毁数据库连接池
void connection_pool::DestroyPool()
{
lock.lock();
if (connList.size() > 0)
{
list<MYSQL *>::iterator it;
for (it = connList.begin(); it != connList.end(); ++it)
{
MYSQL *con = *it;
mysql_close(con);
}
m_CurConn = 0;
m_FreeConn = 0;
connList.clear();
}
lock.unlock();
}
//当前空闲的连接数
int connection_pool::GetFreeConn()
{
return this->m_FreeConn;
}
connection_pool::~connection_pool()
{
DestroyPool();
}
connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool){
*SQL = connPool->GetConnection();
conRAII = *SQL;
poolRAII = connPool;
}
connectionRAII::~connectionRAII(){
poolRAII->ReleaseConnection(conRAII);
}
sql_connection_pool.h
#ifndef _CONNECTION_POOL_
#define _CONNECTION_POOL_
#include <stdio.h>
#include <list>
#include <mysql/mysql.h>
#include <error.h>
#include <string.h>
#include <iostream>
#include <string>
#include "../lock/locker.h"
#include "../log/log.h"
using namespace std;
class connection_pool
{
public:
MYSQL *GetConnection(); //获取一个数据库连接
bool ReleaseConnection(MYSQL *conn); //释放一个数据库连接
int GetFreeConn(); //获取空闲的连接数
void DestroyPool(); //销毁整个连接池
//单例模式
static connection_pool *GetInstance();
void init(string url, string User, string PassWord, string DataBaseName, int Port, int MaxConn, int close_log);
private:
connection_pool();
~connection_pool();
int m_MaxConn; //最大连接数
int m_CurConn; //当前已使用的连接数
int m_FreeConn; //当前空闲的连接数
locker lock; //定义了一个互斥锁
list<MYSQL *> connList; //连接池
sem reserve; //定义了一个信号量,叫reserve.用来记录空闲的连接数
public:
string m_url; //主机地址
string m_Port; //数据库端口号
string m_User; //登陆数据库用户名
string m_PassWord; //登陆数据库密码
string m_DatabaseName; //使用数据库名
int m_close_log; //日志开关
};
class connectionRAII{
public:
connectionRAII(MYSQL **con, connection_pool *connPool);
~connectionRAII();
private:
MYSQL *conRAII;
connection_pool *poolRAII;
};
#endif