引言:
数据库连接池和线程池的思想一样,是为了避免频繁创建和销毁数据库连接导致的性能开销。如果一个项目频繁的需要访问数据库,那么它就有可能需要频繁的创建/销毁数据库连接,那么我们可以采用数据库连接池的技术,在需要时,从数据库连接池中获取数据库连接,在用完数据库连接后再将它重新放回连接池中.
本文章所有代码大都来自开源项目: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
linux MYSQL C API 相关函数
提示:如果想使用linux环境下的MYSQL C API
首先需要确保主机中有mysql
然后在编译时链接 -lmysqlclient库
$(CXX) -o server $^ $(CXXFLAGS) -lpthread -lmysqlclient
类似这样
最后在C程序中引入<mysql/mysql.h>
#include <mysql/mysql.h>
1.mysql_init 初始化一个mysql对象,后续作为其他mysql函数的首个参数
2.mysql_real_connect 创建一个数据库连接
3.mysql_close 关闭一个数据库连接
接下来介绍一下如何使用MYSQL C API来进行sql语句的执行
- mysql_query
int mysql_query(MYSQL *mysql, const char *query);
执行sql查询
其中query放的是sql语句
- mysql_store_result
MYSQL_RES* mysql_store_result(MYSQL* con);
查询sql执行的结果
- mysql_num_rows
int mysql_num_rows(MYSQL_RES* result);
查询执行结果的行数
- mysql_num_fields
int mysql_num_fields(MYSQL_RES* result);
查询执行结果的列数
- mysql_fetch_row
MYSQL_ROW mysql_fetch_row(MYSQL_RES* result);
获取查询结果的一行
调用之后,会自动将指针下移,下次调用时自动获取下一行
打印本次查询结果
MYSQL_RES *result; //查询的结果,使用前需要用mysql_store_result初始化
MYSQL_ROW row; //查询结果中的一行,使用前需要用mysql_fetch_row初始化
// 假设已经执行了SQL查询并获取到了 result
while ((row = mysql_fetch_row(result)) != NULL) {
unsigned int num_fields = mysql_num_fields(result); //获取列数
for (unsigned int i = 0; i < num_fields; i++) {
if (row[i] != NULL) {
printf("%s ", row[i]);
} else {
printf("NULL ");
}
}
printf("\n");
}
- mysql_free_result
void mysql_free_result(MYSQL_RES *result);
释放由先前执行的 MySQL 查询返回的结果集所分配的内存空间。
一个比较完整的示例:
MYSQL_RES* result;
MYSQL_ROW row;
char buf[100];
sprintf(buf, "SELECT * FROM users;"); //这一步如果需要传参的话,后面加上可变参数列标即可
mysql_query(connection, buf); //执行sql语句
result = mysql_store_result(connection); //获取执行结果
if (result == NULL)
{
//结果为空
mysql_free_result(result);
printf("null\n");
return;
}
int r = mysql_num_rows(result);
int c = mysql_num_fields(result);
printf("%d %d\n", r, c);
for (int i = 0; i < r; ++i)
{
row = mysql_fetch_row(result);
for (int j = 0; j < c; ++j)
{
if (row[j] == NULL) printf("null ");
else printf("%s ", row[j]);
}
printf("\n");
}
mysql_free_result(result);
7.mysql_fetch_field
MYSQL_FIELD * mysql_fetch_field(MYSQL_RES *result);
返回结果集中某列字段的属性
MYSQL_RES *res = mysql_store_result(mysql);
MYSQL_FIELD *field;
int num_fields = mysql_num_fields(res);
//遍历每一个字段
for(int i = 0; i < num_fields; i++) {
field = mysql_fetch_field(res);//获取这个字段的属性
if (field) {
printf("Field %u: %s\n", i, field->name);
printf("Field type: %d\n", field->type);
printf("Field length: %lu\n", field->length);
printf("Field flags: %d\n", field->flags);
printf("--------------------------------\n");
}
}
以上介绍的是普通的sql语句执行
接下来介绍mysql预处理语句的相关函数和使用,预处理语句我也没怎么使用过,所以这里可能有错误,仅供参考
- mysql_stmt_init
MYSQL_STMT *mysql_stmt_init(MYSQL *mysql);
初始化一个预处理语句
- mysql_stmt_prepare
int mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, unsigned long length);
其中stmt表示预处理语句
query表示查询语句
length表示查询语句的长度,一般是 strlen(query)(string.h)
为预处理语句绑定一个 SQL 查询语句
- mysql_stmt_bind_param
int mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND *bind);
为预处理语句绑定输入参数
stmt表示预处理语句
MYSQL_BIND是要绑定的参数
MYSQL_BIND param[2]; //假设需要绑定两个参数
其成员如下:
buffer_type: 一个枚举类型,指定参数的数据类型,比如 MYSQL_TYPE_STRING, MYSQL_TYPE_LONG, MYSQL_TYPE_DOUBLE 等。
buffer: 一个指向要绑定参数的内存缓冲区的指针。
buffer_length: 缓冲区的长度,即参数数据的长度。对于字符串类型,通常是字符串的长度;对于数值类型,通常是数据类型的大小。
is_null: 一个指向表示参数是否为 NULL 的变量的指针。如果需要将 NULL 值插入数据库,则需要将此变量设置为 1。
typedef struct st_mysql_bind {
unsigned long *length;
my_bool *is_null;
void *buffer;
enum enum_field_types buffer_type;
unsigned long buffer_length;
my_bool error;
unsigned char row_ptr;
void *extension;
} MYSQL_BIND;
…
- mysql_stmt_execute
int mysql_stmt_execute(MYSQL_STMT *stmt);
执行预处理sql语句
- mysql_stmt_close
void mysql_stmt_close(MYSQL_STMT *stmt);
关闭预处理sql语句
查询sql执行结果使用
- mysql_stmt_bind_result
my_bool mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind);
用于将SQL执行的结果绑定在某些参数上,之后在使用mysql_stmt_fetch之后,当前一行的结果会被绑定到参数集中
- mysql_stmt_store_result
my_bool mysql_stmt_store_result(MYSQL_STMT *stmt);
获取完整结果集,保存到内存中,为后续使用mysql_stmt_fetch作准备
- mysql_stmt_fetch
int mysql_stmt_fetch(MYSQL_STMT *stmt);
获取一行结果集,结果保存在Mysql_stmt_bind_result绑定的变量中
完整使用样例:
#include <mysql/mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
MYSQL *mysql = mysql_init(NULL);
mysql_real_connect(mysql, "localhost", "user", "password", "database", 0, NULL, 0);
MYSQL_STMT *stmt = mysql_stmt_init(mysql); //预处理语句
const char *sql = "SELECT * FROM table_name";
mysql_stmt_prepare(stmt, sql, strlen(sql)); //绑定sql语句,如果有需要,需要用mysql_stem_bind_param函数绑定一下参数
mysql_stmt_execute(stmt);//执行
mysql_stmt_store_result(stmt);//获取结果集
char buffer[256];
unsigned long length;
MYSQL_BIND bind;
bind.buffer_type = MYSQL_TYPE_VAR_STRING;
bind.buffer = &buffer;
bind.length = &length;
mysql_stmt_bind_result(stmt, &bind); //绑定结果到变量
//获取结果集的一行
while (mysql_stmt_fetch(stmt) == 0) {
printf("%s\n", buffer);
}
mysql_stmt_close(stmt);
mysql_close(mysql);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <mysql/mysql.h>
#define DB_HOST "localhost"
#define DB_USER "username"
#define DB_PASS "password"
#define DB_NAME "database_name"
int main() {
MYSQL *conn;
MYSQL_STMT *stmt;
conn = mysql_init(NULL);
if (conn == NULL) {
fprintf(stderr, "mysql_init() failed\n");
exit(1);
}
if (!mysql_real_connect(conn, DB_HOST, DB_USER, DB_PASS, DB_NAME, 0, NULL, 0)) {
fprintf(stderr, "mysql_real_connect() failed: %s\n", mysql_error(conn));
exit(1);
}
// Prepare the statement
stmt = mysql_stmt_init(conn);
if (!stmt) {
fprintf(stderr, "mysql_stmt_init() failed\n");
exit(1);
}
if (mysql_stmt_prepare(stmt, "SELECT user_id, username FROM users WHERE user_id = ?", strlen("SELECT user_id, username FROM users WHERE user_id = ?")) != 0) {
fprintf(stderr, "mysql_stmt_prepare() failed\n");
exit(1);
}
// Bind parameters
int user_id_param = 1;
MYSQL_BIND param_bind;
param_bind.buffer_type = MYSQL_TYPE_LONG;
param_bind.buffer = (void *)&user_id_param;
if (mysql_stmt_bind_param(stmt, ¶m_bind) != 0) {
fprintf(stderr, "mysql_stmt_bind_param() failed\n");
exit(1);
}
if (mysql_stmt_execute(stmt) != 0) {
fprintf(stderr, "mysql_stmt_execute() failed: %s\n", mysql_stmt_error(stmt));
exit(1);
}
// Bind result
MYSQL_BIND result_bind[2];
int user_id;
char username[255];
my_bool is_null[2] = {0, 0};
result_bind[0].buffer_type = MYSQL_TYPE_LONG;
result_bind[0].buffer = (void *)&user_id;
result_bind[0].is_null = &is_null[0];
result_bind[1].buffer_type = MYSQL_TYPE_STRING;
result_bind[1].buffer = username;
result_bind[1].buffer_length = sizeof(username);
result_bind[1].is_null = &is_null[1];
if (mysql_stmt_bind_result(stmt, result_bind) != 0) {
fprintf(stderr, "mysql_stmt_bind_result() failed\n");
exit(1);
}
// Fetch the result set
if (mysql_stmt_store_result(stmt) != 0) {
fprintf(stderr, "mysql_stmt_store_result() failed\n");
exit(1);
}
if (mysql_stmt_fetch(stmt) == 0) {
if (!is_null[0] && !is_null[1]) {
printf("User ID: %d, Username: %s\n", user_id, username);
} else {
fprintf(stderr, "Data is NULL\n");
}
} else {
fprintf(stderr, "mysql_stmt_fetch() failed\n");
}
mysql_stmt_close(stmt);
mysql_close(conn);
return 0;
}
参考链接
预处理语句使用