服务器编程:数据库连接池

引言:
数据库连接池和线程池的思想一样,是为了避免频繁创建和销毁数据库连接导致的性能开销。如果一个项目频繁的需要访问数据库,那么它就有可能需要频繁的创建/销毁数据库连接,那么我们可以采用数据库连接池的技术,在需要时,从数据库连接池中获取数据库连接,在用完数据库连接后再将它重新放回连接池中.

本文章所有代码大都来自开源项目: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语句的执行

  1. mysql_query
int mysql_query(MYSQL *mysql, const char *query);

执行sql查询
其中query放的是sql语句

  1. mysql_store_result
MYSQL_RES* mysql_store_result(MYSQL* con);

查询sql执行的结果

  1. mysql_num_rows
int mysql_num_rows(MYSQL_RES* result);

查询执行结果的行数

  1. mysql_num_fields
int mysql_num_fields(MYSQL_RES* result);

查询执行结果的列数

  1. 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");
}
  1. 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预处理语句的相关函数和使用,预处理语句我也没怎么使用过,所以这里可能有错误,仅供参考

  1. mysql_stmt_init
MYSQL_STMT *mysql_stmt_init(MYSQL *mysql);

初始化一个预处理语句

  1. mysql_stmt_prepare
int mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, unsigned long length);

其中stmt表示预处理语句
query表示查询语句
length表示查询语句的长度,一般是 strlen(query)(string.h)

为预处理语句绑定一个 SQL 查询语句

  1. 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;

  1. mysql_stmt_execute
int mysql_stmt_execute(MYSQL_STMT *stmt);

执行预处理sql语句

  1. mysql_stmt_close
void mysql_stmt_close(MYSQL_STMT *stmt);

关闭预处理sql语句

查询sql执行结果使用

  1. mysql_stmt_bind_result
my_bool mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind);

用于将SQL执行的结果绑定在某些参数上,之后在使用mysql_stmt_fetch之后,当前一行的结果会被绑定到参数集中

  1. mysql_stmt_store_result
my_bool mysql_stmt_store_result(MYSQL_STMT *stmt);

获取完整结果集,保存到内存中,为后续使用mysql_stmt_fetch作准备

  1. 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, &param_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;
}

参考链接
预处理语句使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值