为什么要用数据库连接池?
我们的网站允许用户注册,在没有池的情况下,假设只有一个人,那么流程就是,用户点击注册,通过socket将用户的账号和密码发送到服务器,然后就需要创建MySQL数据库连接,然后插入数据,插入完毕后销毁该连接;然而当用户很多时,这个设计就会花费大量的时间在数据库连接的创建和释放上。为了避免这种情况,引入池的概念,在程序初始化的时候,集中建立多个数据库连接,并集中管理,供程序使用,更见安全和可靠。
池是一组资源的机会,这组资源在服务器启动之初就被完全创建好并初始化,池是资源的容器,实现对资源的复用。是一种用空间换时间的技术。当系统开始处理客户请求的时候,如果需要相关资源,直接从池中获取,无需动态分配,当服务器处理完一个客户连接口,将相关资源放回池中,无需执行系统调用来释放资源。
如何设计?
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;public:string m_url; //主机地址string m_Port; //数据库端口号string m_User; //登陆数据库用户名string m_PassWord; //登陆数据库密码string m_DatabaseName; //使用数据库名int m_close_log; //日志开关
};
在init函数中完成了对各个数据库连接的初始化,并将连接存储到list中,
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);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;}reserve = sem(m_FreeConn);m_MaxConn = m_FreeConn;
}
下面是从池中获取一个连接的函数:
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;
}
可以看到,先调用wait函数,reserve是信号量,如果值>=1,那么将值-1,如果为0就会陷入阻塞。成功获得信号量后,获得互斥锁,然后将池中的list的头取出来,更新相关的数据后释放锁。
释放连接时也是类似的,上锁,然后将连接添加进池中,更新数据后释放锁。
而作者为了实现池的RAII,设计了connectionRAII类:
class connectionRAII{public:connectionRAII(MYSQL **con, connection_pool *connPool);~connectionRAII();private:MYSQL *conRAII;connection_pool *poolRAII;
};
需要说明,由于数据库连接本身就是指针,因此通过有参构造对传入的参数进行修改时需要用双指针。
如图所示,*A的值是0x00000004;*B的值是C的实际内容,那么也就是说*A之后得到0x00000008是实际资源的地址,现在还要"解引用"一次才能得到实际值。
下面的代码是获得池中的一个连接以及将连接还回去的代码。
connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool){*SQL = connPool->GetConnection();conRAII = *SQL;poolRAII = connPool;
}connectionRAII::~connectionRAII(){poolRAII->ReleaseConnection(conRAII);
}