TinywebServer代码详解– 数据库连接池(11)
该blog内容转自:最新版Web服务器项目详解 - 11 数据库连接池
该blog对上述内容进行补充(在本人的角度)
结合此前记录的blog一起学习:牛客WebServer项目实战(点击跳转)
结合此前记录的blog一起学习:多线程与线程同步(点击跳转)
关于数据线程池相关blog(点击跳转)
原项目地址(点击跳转)
博主添加注释后项目地址(点击跳转)
一、基础知识
1.数据库连接池
池是一组资源的集合,这组资源在服务器启动之初就被完全创建好并初始化。通俗来说,池是资源的容器,本质上是对资源的复用
连接池中的资源为一组数据库连接,由程序动态地对池中的连接进行使用,释放。数据库连接池是一种技术,主要用于管理数据库连接,以提高数据库操作的效率和性能。它允许系统预先创建一定数量的数据库连接,并将这些连接存储在一个“池”中。当应用程序需要访问数据库时,它可以直接从池中取出一个现有的连接,使用完毕后再放回池中,而不是每次需要时都创建和销毁连接
主要优势:
资源重用:重复使用现有的数据库连接,减少创建和销毁连接的开销
提高性能:减少数据库连接的开销可以显著提高应用程序的性能
控制并发:连接池可以限制系统中数据库连接的最大数量,避免过多的并发连接导致数据库崩溃
简化连接管理:开发者只需关心如何从连接池中获取和返回连接,无需手动管理每个数据库连接的生命周期
2.数据库访问流程
连接数据库:
- 首先,需要通过数据库驱动或API建立与数据库的连接。在使用连接池的情况下,这通常意味着从连接池中获取一个可用的连接
执行查询或更新:
- 通过连接执行SQL命令。这些命令可以是数据查询(SELECT)、数据更新(INSERT、UPDATE、DELETE)或其他数据库操作(如事务控制)
- SQL命令可以直接编写,或者通过使用预编译的查询来提高性能和安全性(例如,使用预编译的语句可以防止SQL注入攻击)
处理结果:
- 如果执行的是查询操作,需要处理返回的数据。这通常涉及从数据库获取结果集,并在应用程序中对其进行迭代,以读取单行或多行数据
- 对于更新操作,可能需要检查操作的影响(例如,影响的行数)
关闭连接:
- 在数据访问完成后,及时关闭数据库连接是很重要的。这通常意味着将连接返回到连接池中,供后续使用
- 确保释放资源,如结果集和数据库连接对象,以避免资源泄露和其他潜在问题
异常处理:
- 在整个数据库操作过程中,应妥善处理可能出现的异常或错误,例如连接失败、SQL错误等
- 通常需要在代码中添加错误处理逻辑,以确保即使在出现错误的情况下,程序也能正常运行,同时保证所有资源都被正确清理
事务管理:
- 对于需要多个步骤共同完成的数据库操作,常常需要用到事务管理。事务确保这些步骤要么全部完成,要么全部不做,这是通过事务的提交(commit)和回滚(rollback)操作来控制的
3.为什么创建数据库连接池
从一般流程中可以看出,若系统需要频繁访问数据库,则需要频繁创建和断开数据库连接,而创建数据库连接是一个很耗时的操作,也容易对数据库造成安全隐患
在程序初始化的时候,集中创建多个数据库连接,并把他们集中管理,供程序使用,可以保证较快的数据库读写速度,更加安全可靠。可以实现资源重用、提高性能、控制并发
二、章节内容
1.整体概述
池可以看做资源的容器,所以多种实现方法,比如数组、链表、队列等。这里,使用单例模式和链表创建数据库连接池,实现对数据库连接资源的复用
项目中的数据库模块分为两部分
其一,是数据库连接池的定义
其二,是利用连接池完成登录和注册的校验功能
具体的,工作线程从数据库连接池取得一个连接,访问数据库中的数据,访问完毕后将连接交还连接池
2.本文内容
本篇将介绍数据库连接池的定义,具体的涉及到单例模式创建、连接池代码实现、RAII机制释放数据库连接
单例模式创建,结合代码描述连接池的单例实现
连接池代码实现,结合代码对连接池的外部访问接口进行详解
RAII机制释放数据库连接,描述连接释放的封装逻辑
三、代码实现
1.数据库连接池的定义
使用局部静态变量的懒汉式单例模式,创建数据库连接池,C++11之后局部静态变量是线程安全的
CGImysql/sql_connection_pool.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| #pragma once #include <stdio.h> #include <list> #include <mysql/mysql.h> #include <string.h> #include <string> #include "../lock/locker.h" #include "../log/log.h"
class connection_pool { public: static connection_pool* GetInstance(); MYSQL *GetConnection(); bool ReleaseConnection(MYSQL *conn); int GetFreeConn(); void DestroyPool(); void init(std::string url, std::string User, std::string PassWord, std::string DataBaseName, int Port, int MaxConn, int close_log);
private: connection_pool(); ~connection_pool(); connection_pool(const connection_pool &) = delete; connection_pool& operator=(const connection_pool &) = delete;
int m_MaxConn; int m_CurConn; int m_FreeConn; locker lock; sem reserve; list<MYSQL *> connList;
public: string m_url; string m_Port; string m_User; string m_PassWord; string m_DatabaseName; int m_close_log; };
|
2.连接池代码实现
连接池的功能主要有:初始化,获取连接、释放连接,销毁连接池
(1)初始化
CGImysql/sql_connection_pool.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| #include <stdio.h> #include <string> #include <string.h> #include <stdlib.h> #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);
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; }
int connection_pool::GetFreeConn() { return this->m_FreeConn; }
connection_pool::~connection_pool() { DestroyPool(); }
|
(2)获取、释放连接
当线程数量大于数据库连接数量时,使用信号量进行同步,每次取出连接,信号量原子减1,释放连接原子加1,若连接池内没有连接了,则阻塞等待
另外,由于多线程操作连接池,会造成竞争,这里使用互斥锁完成同步,具体的同步机制均使用lock.h
中封装好的类
CGImysql/sql_connection_pool.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
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; }
|
(3)销毁连接池
通过迭代器遍历连接池链表,关闭对应数据库连接,清空链表并重置空闲连接和现有连接数量
CGImysql/sql_connection_pool.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
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(); }
|
3.RAII机制释放数据库连接
在 C++ 中,RAII(Resource Acquisition Is Initialization
)是一种常用的编程模式,主要用于资源管理。RAII 通过将资源(如动态内存、文件句柄、网络连接等)的生命周期绑定到对象的生命周期,以确保在对象被销毁时资源能够自动释放
RAII 的核心思想是利用局部对象的构造和析构机制来管理资源。当一个对象被创建时,它的构造函数负责获取必要的资源,并在对象的析构函数中释放这些资源。这样,无论函数通过何种路径退出(正常退出、返回前的退出、异常抛出),都能保证资源的正确释放
将数据库连接的获取与释放(归还)通过RAII机制封装,避免手动释放
(1)定义
这里需要注意的是,在获取连接时,通过有参构造对传入的参数进行修改。其中数据库连接本身是指针类型,所以参数需要通过双指针才能对其进行修改
通过RAII机制管理连接,当数据库连接完成任务之后,利用RAII机制的析构机制,将数据库连接归还至队列
CGImysql/sql_connection_pool.h
1 2 3 4 5 6 7 8 9 10 11
| class connectionRAII { public: connectionRAII(MYSQL **con,connection_pool *connectionPool); ~connectionRAII(); private: MYSQL *conRAII; connection_pool *poolRAII; };
|
(2)实现
CGImysql/sql_connection_pool.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool) { *SQL = connPool->GetConnection(); conRAII = *SQL; poolRAII = connPool; }
connectionRAII::~connectionRAII() { poolRAII->ReleaseConnection(conRAII); }
|