数据库连接池

发布时间:2026/8/7 7:28:58
数据库连接池 引言本篇文章介绍一个四大池中的数据库连接池里面会涉及到信号队列智能指针对数据库操作的二次封装c接口等一些基本的操作实现连接池的四个必备的组成部分初始连接量最大连接量最大空闲时间和超时连接时间。简介我们为什么要数据库连接池解决的问题主要是高并发的场景。当同一时间大量的连接请求访问数据库的时候如果没有数据库连接池那么我们的数据库就会创建上万个socket来和客户端进行连接这对于性能的消耗是非常大的。而且对于TCP来说我们需要有三次握手和四次挥手这种耗时的操作也会导致网络的卡顿每当数据的发出我们都需要三次握手和四次挥手。所以我们参考线程池的特点我们可以创建一个队列来存储一定量的connection当有客户端访问的时候我们的connection可以直接分配这样子我们我们只需要建立依次连接拿到对应的connection之后发送的信息全部都可以通过已有的连接发送信息。当客户量极大幅度增加的时候我们就需要扩容我们的连接池但是扩容是有一个限度的我们记作maxConnectionSize但是当用户量变少之后我们不再需要这么多connection我们就要减少这些数量恢复到刚开始那这个恢复的条件就是一个计时的过程我们这里可以设置成100ms。当客户量下降了之后我们就不再需要这么多的连接量了所以这个时候我们通过计时来判断什么时候可以把connection的数量恢复到最初值。最后我们的连接断开之后我们需要回到队列里面所以我们需要重写智能指针的析构函数。代码首先我们写一个处理数据库c接口的一个模板除此之外对于每一个连接我们都需要设置几个函数来处理如果一段时间之后连接池connection数量恢复到初始化一个是得到连接的起始值一个是记录这个连接最新存在的时间。#pragma once #ifndef _CONNECTION_H_ #define _CONNECTION_H_ #include mysql.h #include string #include chrono namespace Litguidyo { class Connection { public: Connection(); ~Connection(); bool connect(std::string ip, unsigned short port, std::string user, std::string password, std::string dbname); bool update(std::string sql); MYSQL_RES* query(std::string sql); void refreshAliveTime() { _aliveTime std::chrono::steady_clock::now(); } std::chrono::milliseconds getAliveTime(); private: MYSQL* _connection; // MySQL和Server的一个链接 std::chrono::steady_clock::time_point _aliveTime; // 记录进入空闲状态的存活时间 _ }; } #endif#include Connection.h #include ../Util/Helper.h Litguidyo::Connection::Connection() { _connection mysql_init(nullptr); } Litguidyo::Connection::~Connection() { if (_connection ! nullptr) { mysql_close(_connection); } } bool Litguidyo::Connection::connect(std::string ip, unsigned short port, std::string user, std::string password, std::string dbname) { MYSQL* p mysql_real_connect(_connection, ip.c_str(), user.c_str(), password.c_str(), dbname.c_str(), port, nullptr, 0); return p ! nullptr; } bool Litguidyo::Connection::update(std::string sql) { if (mysql_query(_connection, sql.c_str())) { // LOG(update failed sql); return false; } return true; } MYSQL_RES* Litguidyo::Connection::query(std::string sql) { if (mysql_query(_connection, sql.c_str())) { LOG(query failed sql); return nullptr; } return mysql_use_result(_connection); } std::chrono::milliseconds Litguidyo::Connection::getAliveTime() { return std::chrono::duration_caststd::chrono::milliseconds(std::chrono::steady_clock::now() - _aliveTime); }我们这里使用单例模式懒汉模式然后连接池里面记录了数据库的所有基本的信息方便客户端的连接。我们扩展连接数量的方式采用的是生产者-消费者模型引入的是消息队列#pragma once #include queue #include ../Connection/Connection.h #include vcruntime.h #include mutex #include atomic #include thread #include condition_variable #include memory #ifndef _CONNECTIONPOOL_H_ #define _CONNECTIONPOOL_H_ namespace Litguidyo { class ConnectionPool { private: ConnectionPool(); ConnectionPool(std::string path); ConnectionPool(const ConnectionPool) delete; ConnectionPool operator(const ConnectionPool) delete; public: static ConnectionPool* getInstance(); std::shared_ptrConnection getConnection(); private: std::string _ip; std::string _username; std::string _password; unsigned short _port; // 3306 size_t _initConnectionSize; size_t _maxConnectionSize; size_t _maxIdleTime; // 连接池最大空闲时间 size_t _connectionTimeout; // 连接池最大超时时间 std::string _dbname; // 数据库名 std::queueConnection* _connectionQueue; std::mutex _queueMutex; // 维护队列线程安全 std::atomicsize_t _currentConnectionSize; // 当前连接池中连接数 bool loadConfigPool(); void produceConnectionTask(); // 运行在独立线程中专门负责生成新连接 std::condition_variable cv; // 设置条件变量用于连接生产线程和消费线程的通信 void scannerConnectionTask(); // 运行在独立线程中专门负责扫描超过maxIdleTime时间的空闲连接进行对于连接的回收 }; } #endif初始化先创建少量的连接数量同时构建两个线程一个线程专门创建连接为了应对connection不够的情况一个线程专门扫描队列把那些超时的连接全部断开然后重新放入队列里面。Litguidyo::ConnectionPool::ConnectionPool() { if (!loadConfigPool()) { return; } for (size_t i 0; i _initConnectionSize; i) { Connection* connection new Connection(); connection-connect(_ip, _port, _username, _password, _dbname); connection-refreshAliveTime(); // 刷新起始时间 _connectionQueue.push(connection); _currentConnectionSize; } std::thread produce(std::bind(ConnectionPool::produceConnectionTask, this)); produce.detach(); // 启动一个新的线程扫描超过maxIdleTime时间的空闲连接进行对于连接的回收 std::thread scanner(std::bind(ConnectionPool::scannerConnectionTask, this)); scanner.detach(); }按照代码的思路我们取到connection之后什么时候释放应该是由客户端来决定所以选择的是智能指针但是当释放了之后并不是delete而是重新放回队列里面所以要重写析构函数。我们取connection的时候有可能里面已经没有connection了所以我们需要判断一下不过不是没有就立马返回客户端而是等待100ms持有锁这个中间如果有connection被释放了那么就会立刻跳过if执行while循环然后跳出。std::shared_ptrLitguidyo::Connection Litguidyo::ConnectionPool::getConnection() { std::unique_lockstd::mutex lock(_queueMutex); while (_connectionQueue.empty()) { // 让线程先等待100ms如果100ms内没有连接就继续执行代码 if (std::cv_status::timeout cv.wait_for(lock, std::chrono::microseconds(_connectionTimeout))) { if (_connectionQueue.empty()) { LOG(ConnectionPool is empty, please check the configuration file! ); return nullptr; } } } std::shared_ptrConnection sp(_connectionQueue.front(), [](Connection* p) { std::lock_guardstd::mutex lock(_queueMutex); p-refreshAliveTime(); _connectionQueue.push(p); }); _connectionQueue.pop(); if (_connectionQueue.empty()) { cv.notify_all(); // 通知消费线程 } return sp; }只有队列是空的才会生产connection不够用了并且还不可以超过最大的值当生产完了之火唤醒消费者。因为这里只有一个生产线程所以完全不需要担心通知到生产线程并且有while来保证安全。void Litguidyo::ConnectionPool::produceConnectionTask() { while(true) { std::unique_lockstd::mutex lock(_queueMutex); while (!_connectionQueue.empty()) { cv.wait(lock); // 生产线程进行等待 } if (_currentConnectionSize _maxConnectionSize) { Connection* connection new Connection(); connection-refreshAliveTime(); // 刷新起始时间 connection-connect(_ip, _port, _username, _password, _dbname); _connectionQueue.push(connection); _currentConnectionSize; } cv.notify_all(); // 通知消费线程 } }这个线程不是一直循环而是隔一段时间扫描一次如果发现队列里面的数量已经超过了初始化的数量而且存活时间已经超过了限制这个connection没有被长时间使用就会删除。void Litguidyo::ConnectionPool::scannerConnectionTask() { while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(_maxIdleTime)); std::unique_lockstd::mutex lock(_queueMutex); while (_currentConnectionSize _initConnectionSize) { Connection* temp _connectionQueue.front(); if (temp-getAliveTime() std::chrono::milliseconds(_maxIdleTime)) { _connectionQueue.pop(); _currentConnectionSize--; delete temp; } else { break; // 队头都没有超时那么之后的连接都不可能超时不需要继续扫描 } } } }总结本篇文章到这里就结束了希望可以帮助大家理解~~~