大纲 前言 在 C/C++ 项目中,为了提高 MySQL Server 的访问效率,基于 C++ 11 实现数据库连接池,并使用 MySQL Connector/C++ 库。
项目背景 为了解决 MySQL 数据库(基于 C/S 设计)的访问瓶颈,除了在服务器端增加缓存服务器缓存常用的数据之外(例如 Redis),还可以增加连接池,来提高 MySQL Server 的访问效率。在高并发情况下,大量的 TCP 三次握手、MySQL Server 连接认证、MySQL Server 关闭连接回收资源和 TCP 四次挥手所耗费的性能时间也是很明显的,增加连接池就是为了减少这一部分的性能损耗。在市场上比较流行的连接池包括 C3P0、Apache DBCP、HikariCP、阿里巴巴的 Druid 连接池,它们对于短时间内大量的数据库增删改查操作性能的提升是很明显的,但是它们有一个共同点就是,全部都是由 Java 实现的。
关键技术 基于 C++ 11 实现数据库连接池时,使用到的技术栈如下:
单例模式 Lambda 表达式 队列容器 queue
智能指针 shared_ptr
基于 CAS 的原子基础类型 MySQL 数据库编程(基于 MySQL Connector/C++ 库) C++ 11 的多线程编程,包括线程互斥、线程同步通信等 生产者 - 消费者线程模型的实现,基于 mutex
、unique_lock
、condition_variable
版本说明 本文使用的各软件版本如下所示:
软件 版本 C++ 11
Boost 1_78_0
MySQL Connector/C++ 1.1.13
G++(GCC) 4.8.5
CMake 3.25.1
MySQL 5.7.33
Clion 2019.3
Linux CentOS 7.9
兼容平台 (1) 有关 MySQL 数据库编程、多线程编程、线程互斥、线程同步通信、智能指针、设计模式、容器等等技术在 C++ 语言层面都可以直接实现;因此,数据库连接池代码是同时兼容 Windows 和 Linux 平台的,只是不同平台的 CMake 配置稍微有一点区别。 (2) 由于 MySQL 在 Linux 平台上使用得比较多,因此默认选择在 Linux 平台上进行开发,并使用 CMake 和 GCC 直接编译运行项目。 准备工作 安装 Boost 由于 MySQL Connector/C++ 依赖了 Boost,因此本地 Linux 系统需要安装 Boost。建议从 Boost 官网 下载 Boost 的源码压缩包,然后使用 root
用户手动编译安装 Boost,此方式适用于绝大多数 Linux 系统,如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ wget https://boostorg.jfrog.io/artifactory/main/release/1.78.0/source /boost_1_78_0.tar.gz $ tar -xvf boost_1_78_0.tar.gz $ cd boost_1_78_0 $ sudo ./bootstrap.sh --prefix=/usr/local /boost $ sudo ./b2 install --prefix=/usr/local /boost --with=all
安装 MySQL Connector/C++ (1) Linux 平台上,可以在 MySQL 官网 直接下载 1.1.13
版本的 MySQL Connector/C++,下载完成后直接解压文件。 (2) 将解压得到的 include
和 lib
目录拷贝到数据库连接池项目中,然后配置 CMake 加载 MySQL Connector/C++ 的头文件和动态链接库即可。 连接池的功能介绍 连接池一般包含了数据库连接所用的 IP 地址、Port 端口号、用户名和密码以及其它的性能参数,例如初始连接数、最大连接数、最大空闲时间、连接超时时间等。本项目是基于 C++ 语言实现的连接池,主要也是实现以上几个所有连接池都支持的通用基础功能,其余连接池更多的扩展功能,可以自行实现。
连接池的功能设计 C++ 源文件的功能划分
MysqlConnection.h
和 MysqlConnection.cpp
:数据库增删改查的代码实现MysqlConnectionPool.h
和 MysqlConnectionPool.cpp
:连接池的代码实现连接池的实现主要包含了以下功能
(1) 连接池只需要一个实例,所以 ConnectionPool 以单例模式进行设计。 (2) 应用可以从 ConnectionPool 中获取 MySQL 的连接 Connection。 (3) 空闲连接 Connection 全部存储在一个线程安全的 Connection 队列中,使用互斥锁来保证队列的线程安全。 (4) 如果 Connection 队列为空,应用还需要再获取连接,此时需要动态创建连接,最大的连接数量是 maxSize。 (5) 当队列中空闲连接的存活时间超过 maxIdleTime 后,连接就要被释放掉,只保留初始的 initSize 个连接就可以,这个功能需要放在独立的线程中去完成(定时扫描连接)。 (6) 如果 Connection 队列为空,而且当前已创建的连接的数量已达到上限 maxSize,则应用需要等待 connectionTimeout 时间。如果应用还是获取不到空闲的连接,则获取连接失败;此处从 Connection 队列获取空闲连接时,可以使用带超时时间的 mutex
互斥锁来实现连接超时时间。 (7) 应用获取的连接用 shared_ptr
智能指针来管理,并用 Lambda 表达式定制连接释放的功能(不真正释放连接,而是将连接归还到 Connection 队列中)。 (8) 连接的生产和连接的消费采用生产者 - 消费者线程模型来设计,使用了线程间的同步通信机制、条件变量和互斥锁。 连接池的代码实现 连接池的项目结构
目录名称 说明 bin
存放 CMake 编译生成的可执行文件 config
存放数据库连接池的配置文件 include
存放数据库连接池的 C++ 头文件 src
存放数据库连接池的 C++ 源文件 sql
存放 MySQL 数据库初始化的 SQL 脚本 script
存放 Linux 平台自动编译构建项目的 Shell 脚本 libs
存放项目依赖的第三方静态库和动态链接库 mysql-connector
存放 MySQL Connector/C++ 库的头文件和动态链接库(适用于 Linux 平台)
MySQL Connector/C++ 库的头文件和动态链接库(Linux 平台)如下所示:
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 mysql-connector ├── include │ ├── cppconn │ │ ├── build_config.h │ │ ├── config.h │ │ ├── connection.h │ │ ├── datatype.h │ │ ├── driver.h │ │ ├── exception.h │ │ ├── metadata.h │ │ ├── parameter_metadata.h │ │ ├── prepared_statement.h │ │ ├── resultset.h │ │ ├── resultset_metadata.h │ │ ├── sqlstring.h │ │ ├── statement.h │ │ ├── variant.h │ │ ├── version_info.h │ │ └── warning.h │ ├── mysql_connection.h │ ├── mysql_driver.h │ └── mysql_error.h └── lib ├── libcrypto.so -> libcrypto.so.1.1 ├── libcrypto.so.1.1 ├── libmysqlcppconn.so -> libmysqlcppconn.so.7 ├── libmysqlcppconn.so.7 -> libmysqlcppconn.so.7.1.1.13 ├── libmysqlcppconn.so.7.1.1.13 ├── libssl.so -> libssl.so.1.1 └── libssl.so.1.1
提示
MySQL Connector/C++ 库的使用教程请看 这里 。 连接池的配置参数 数据库连接池的配置参数格式是 key=value
,默认存放在 config
目录下的 mysql.ini
文件中,其配置内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 host =127.0 .0.1 :3306 username =rootpassword =123456 dbname =cxx_studyinitSize =10 maxSize =100 maxIdleTime =5 connectionTimeout =500
连接池的 CMake 配置 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 cmake_minimum_required (VERSION 3.15 )set (PATH_TO_BOOST /usr/local/boost)set (PATH_TO_MYSQL_CONNECTOR ./libs/mysql-connector)project (db_connection_pool)set (CMAKE_CXX_STANDARD 11 )set (PROJECT_BINARY_DIR ${PROJECT_SOURCE_DIR} /build)set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR} /bin)add_custom_target (clean_bin ALL COMMAND ${CMAKE_COMMAND} -E remove_directory ${EXECUTABLE_OUTPUT_PATH} COMMAND ${CMAKE_COMMAND} -E make_directory ${EXECUTABLE_OUTPUT_PATH} COMMENT "Cleaning bin directory before build" ) add_custom_command ( TARGET clean_bin POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR} /config/mysql.ini ${EXECUTABLE_OUTPUT_PATH} /mysql.ini COMMENT "Copying mysql.ini to bin directory before build" ) include_directories (${PROJECT_SOURCE_DIR} /include )aux_source_directory (${PROJECT_SOURCE_DIR} /src MAIN_SOURCES)include_directories (${PATH_TO_BOOST} /include ${PATH_TO_MYSQL_CONNECTOR} /include )link_directories (${PATH_TO_BOOST} /lib ${PATH_TO_MYSQL_CONNECTOR} /lib)add_executable (${PROJECT_NAME} _test ${MAIN_SOURCES} )set (CMAKE_CXX_FLAGS "-lpthread -lssl -lcrypto -lboost_system -lboost_filesystem" )target_link_libraries (${PROJECT_NAME} _test ssl.so crypto.so mysqlcppconn.so)
连接池的 C++ 头文件 1 2 3 4 5 6 7 #pragma once #ifdef WIN32 #define LOG(format, ...) printf(format, __VA_ARGS__); #else #define LOG(format, ...) printf(format, ##__VA_ARGS__); #endif
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 #pragma once #include <iostream> #include <vector> #include <mysql_connection.h> #include <cppconn/driver.h> #include <cppconn/exception.h> #include <cppconn/resultset.h> #include <cppconn/statement.h> #include <cppconn/prepared_statement.h> #include <chrono> #include "public.h" using namespace std;using namespace sql;typedef chrono::system_clock::time_point time_point;class MysqlConnection {public : MysqlConnection (); ~MysqlConnection (); public : bool execute (const char *sql) ; int executeUpdate (const char *sql) ; unique_ptr<ResultSet> query (const char *query, const vector<string> parameters) ; bool connect (const string host, const string username, const string password, const string dbname) ; void refreshAliveTime () ; long getAliveTime () const ; private : string _host; string _username; string _password; string _dbname; Driver *_driver; Connection *_connection; time_point _aliveTime; };
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 #pragma once #include <iostream> #include <queue> #include <mutex> #include <atomic> #include <thread> #include <memory> #include <functional> #include <condition_variable> #include "public.h" #include "MysqlConnection.h" using namespace std;class MysqlConnectionPool {public : ~MysqlConnectionPool (); void close () ; bool isClosed () const ; int getSize () const ; static MysqlConnectionPool *getInstance () ; shared_ptr<MysqlConnection> getConnection () ; private : MysqlConnectionPool (); MysqlConnectionPool (const MysqlConnectionPool &) = delete ; MysqlConnectionPool &operator =(const MysqlConnectionPool &) = delete ; bool loadConfigFile () ; void produceConnection () ; void scanIdleConnection () ; static MysqlConnectionPool *INSTANCE; string _host; string _username; string _password; string _dbname; int _initSize; int _maxSize; int _maxIdleTime; int _connectionTimeout; atomic_int _connectionCount; queue<MysqlConnection *> _connectionQueue; mutex _queueMutex; condition_variable _cv; atomic_bool _closed; };
连接池的 C++ 源文件 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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 #include "MysqlConnection.h" MysqlConnection::MysqlConnection () { } MysqlConnection::~MysqlConnection () { if (this ->_connection && !this ->_connection->isClosed ()) { this ->_connection->close (); this ->_connection = nullptr ; } } bool MysqlConnection::execute (const char *sql) { try { if (this ->_connection) { unique_ptr<Statement> statement (this ->_connection->createStatement()) ; if (statement) { return statement->execute (sql); } } } catch (SQLException &e) { LOG ("# ERR: SQLException in %s(%s) on line %d \n" , __FILE__, __FUNCTION__, __LINE__); LOG ("# ERR: MySQL Error Code %d\n" , e.getErrorCode ()); LOG ("# ERR: %s\n" , e.what ()); } return false ; } int MysqlConnection::executeUpdate (const char *sql) { try { if (this ->_connection) { unique_ptr<Statement> statement (this ->_connection->createStatement()) ; if (statement) { return statement->executeUpdate (sql); } } } catch (SQLException &e) { LOG ("# ERR: SQLException in %s(%s) on line %d \n" , __FILE__, __FUNCTION__, __LINE__); LOG ("# ERR: MySQL Error Code %d\n" , e.getErrorCode ()); LOG ("# ERR: %s\n" , e.what ()); } return 0 ; } unique_ptr<ResultSet> MysqlConnection::query (const char *sql, const vector<string> parameters) { unique_ptr<ResultSet> resultSet = nullptr ; try { if (this ->_connection) { int index = 0 ; unique_ptr<PreparedStatement> statement (this ->_connection->prepareStatement(sql)) ; if (statement) { for (auto iterator = parameters.cbegin (); iterator != parameters.cend (); iterator++) { index++; statement->setString (index, (*iterator).c_str ()); } resultSet.reset (statement->executeQuery ()); } } } catch (SQLException &e) { LOG ("# ERR: SQLException in %s(%s) on line %d \n" , __FILE__, __FUNCTION__, __LINE__); LOG ("# ERR: MySQL Error Code %d\n" , e.getErrorCode ()); LOG ("# ERR: %s\n" , e.what ()); } return resultSet; } bool MysqlConnection::connect (const string host, const string username, const string password, const string dbname) { this ->_host = "tcp://" + host; this ->_username = username; this ->_password = password; this ->_dbname = dbname; try { this ->_driver = get_driver_instance (); if (!this ->_driver) { LOG ("# ERR: %s\n" , "Failed to load mysql _driver" ); return false ; } this ->_connection = _driver->connect (this ->_host.c_str (), this ->_username.c_str (), this ->_password.c_str ()); if (!this ->_connection) { LOG ("# ERR: %s\n" , "Failed to connect mysql server" ); return false ; } else { this ->_connection->setSchema (this ->_dbname.c_str ()); return true ; } } catch (SQLException &e) { LOG ("# ERR: SQLException in %s(%s) on line %d \n" , __FILE__, __FUNCTION__, __LINE__); LOG ("# ERR: MySQL Error Code %d\n" , e.getErrorCode ()); LOG ("# ERR: %s\n" , e.what ()); } return false ; } void MysqlConnection::refreshAliveTime () { this ->_aliveTime = chrono::system_clock::now (); } long MysqlConnection::getAliveTime () const { chrono::milliseconds active_timestamp_ms = chrono::duration_cast<chrono::milliseconds>(this ->_aliveTime.time_since_epoch ()); chrono::milliseconds now_timestamp_ms = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now ().time_since_epoch ()); return now_timestamp_ms.count () - active_timestamp_ms.count (); }
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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 #include <boost/filesystem.hpp> #include "MysqlConnectionPool.h" namespace fs = boost::filesystem;MysqlConnectionPool::MysqlConnectionPool () : _connectionCount(0 ), _closed(false ) { if (!loadConfigFile ()) { LOG ("# ERR: %s\n" , "Failed to load config file mysql.ini" ); return ; } for (int i = 0 ; i < this ->_initSize; i++) { MysqlConnection *connection = new MysqlConnection (); bool connected = connection->connect (this ->_host, this ->_username, this ->_password, this ->_dbname); if (connected) { connection->refreshAliveTime (); this ->_connectionQueue.push (connection); this ->_connectionCount++; } } thread produce (bind(&MysqlConnectionPool::produceConnection, this )) ; produce.detach (); thread scan (bind(&MysqlConnectionPool::scanIdleConnection, this )) ; scan.detach (); } MysqlConnectionPool::~MysqlConnectionPool () { this ->close (); } MysqlConnectionPool *MysqlConnectionPool::getInstance () { return INSTANCE; } bool MysqlConnectionPool::loadConfigFile () { fs::path configPath = fs::current_path ().concat ("/mysql.ini" ); FILE *file = fopen (configPath.c_str (), "r" ); if (file == nullptr ) { LOG ("# ERR: %s %s\n" , configPath.c_str (), "file is not exist" ); return false ; } LOG ("======== mysql.ini ========\n" ) while (!feof (file)) { char buffer[1024 ] = {0 }; fgets (buffer, 1024 , file); string line = buffer; int index = line.find ('=' , 0 ); if (index == -1 ) { continue ; } int endIndex = line.find ('\n' , index); string key = line.substr (0 , index); string value = line.substr (index + 1 , endIndex - index - 1 ); if (key == "host" ) { this ->_host = value; } else if (key == "username" ) { this ->_username = value; } else if (key == "password" ) { this ->_password = value; } else if (key == "dbname" ) { this ->_dbname = value; } else if (key == "initSize" ) { this ->_initSize = stoi (value); } else if (key == "maxSize" ) { this ->_maxSize = stoi (value); } else if (key == "maxIdleTime" ) { this ->_maxIdleTime = stoi (value); } else if (key == "connectionTimeout" ) { this ->_connectionTimeout = stoi (value); } LOG ("%s=%s\n" , key.c_str (), value.c_str ()); } LOG ("======== mysql.ini ========\n\n" ) fclose (file); return true ; } void MysqlConnectionPool::close () { if (this ->_closed) { return ; } this ->_closed = true ; unique_lock<mutex> lock (this ->_queueMutex) ; while (!(this ->_connectionQueue.empty ())) { MysqlConnection *phead = this ->_connectionQueue.front (); this ->_connectionQueue.pop (); this ->_connectionCount--; delete phead; } } bool MysqlConnectionPool::isClosed () const { return this ->_closed; } int MysqlConnectionPool::getSize () const { return this ->_connectionCount; } shared_ptr<MysqlConnection> MysqlConnectionPool::getConnection () { if (this ->_closed) { LOG ("# ERR: %s\n" , "Connection pool has closed" ); return nullptr ; } unique_lock<mutex> lock (this ->_queueMutex) ; while (this ->_connectionQueue.empty ()) { cv_status status = this ->_cv.wait_for (lock, chrono::milliseconds (this ->_connectionTimeout)); if (cv_status::timeout == status) { if (this ->_connectionQueue.empty ()) { LOG ("# ERR: %s\n" , "Failed to get connection, queue is empty" ); return nullptr ; } } } shared_ptr<MysqlConnection> sp (this ->_connectionQueue.front(), [&](MysqlConnection *pcon) -> void { unique_lock<mutex> lock(this ->_queueMutex); pcon->refreshAliveTime(); this ->_connectionQueue.push(pcon); this ->_connectionCount++; }) ; this ->_connectionQueue.pop (); this ->_connectionCount--; if (this ->_connectionQueue.empty ()) { this ->_cv.notify_all (); } return sp; } void MysqlConnectionPool::produceConnection () { while (!this ->_closed) { unique_lock<mutex> lock (this ->_queueMutex) ; while (!(this ->_connectionQueue.empty ())) { this ->_cv.wait (lock); } if (this ->_connectionCount < this ->_maxSize) { MysqlConnection *connection = new MysqlConnection (); bool connected = connection->connect (this ->_host, this ->_username, this ->_password, this ->_dbname); if (connected) { connection->refreshAliveTime (); this ->_connectionQueue.push (connection); this ->_connectionCount++; } } this ->_cv.notify_all (); } } void MysqlConnectionPool::scanIdleConnection () { while (!this ->_closed) { this_thread::sleep_for (chrono::seconds (this ->_maxIdleTime)); unique_lock<mutex> lock (this ->_queueMutex) ; while (this ->_connectionCount <= this ->_initSize) { this ->_cv.wait (lock); } while (this ->_connectionCount > this ->_initSize) { MysqlConnection *phead = this ->_connectionQueue.front (); if (phead->getAliveTime () >= this ->_maxIdleTime) { this ->_connectionQueue.pop (); this ->_connectionCount--; delete phead; } else { break ; } } } } MysqlConnectionPool *MysqlConnectionPool::INSTANCE = new MysqlConnectionPool ();
连接池的代码测试 提示
本文的所有 C++ 代码都已经在 Linux 平台下编译并测试通过(基于 Clion 与 G++ 编译器),由于笔者的技术水平有限,暂时无法保证代码没有潜在的 Bug,因此所有 C++ 代码仅供学习参考。
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 DROP DATABASE IF EXISTS `cxx_study`;CREATE DATABASE `cxx_study` DEFAULT CHARACTER SET UTF8;USE `cxx_study`; DROP TABLE IF EXISTS `properties`;CREATE TABLE `properties` ( `ID` int (11 ) NOT NULL AUTO_INCREMENT, `KEY` varchar (200 ) DEFAULT NULL , `VALUE ` varchar (200 ) DEFAULT NULL , `REMARK` varchar (200 ) DEFAULT NULL , PRIMARY KEY (`ID`) USING BTREE ) ENGINE= InnoDB AUTO_INCREMENT= 27 DEFAULT CHARSET= UTF8 ROW_FORMAT= DYNAMIC ; INSERT INTO `properties` (`KEY`, `VALUE `, `REMARK`) VALUES ('test_limit_number' , '430' , 'Limit Number' );INSERT INTO `properties` (`KEY`, `VALUE `, `REMARK`) VALUES ('test_limit_balance' , '929.32' , 'Limit Balance' );
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 #include <iostream> #include "MysqlConnection.h" #include "MysqlConnectionPool.h" using namespace std;void testQuerySingleThread () { MysqlConnection *connection = new MysqlConnection (); connection->connect ("192.168.56.112:3307" , "root" , "123456" , "cxx_study" ); const string querySql = "select * from properties where `KEY` = ?" ; unique_ptr<ResultSet> result = connection->query (querySql.c_str (), {"test_limit_number" }); if (result) { cout << "Query: " << querySql << endl; while (result->next ()) { cout << result->getInt ("ID" ) << " | " ; cout << result->getString ("KEY" ).c_str () << " | " ; cout << result->getString ("VALUE" ).c_str () << " | " ; cout << result->getString ("REMARK" ).c_str () << " | " ; cout << endl; } } delete connection; } void testConnectionPoolSingleThread () { const string insertSql = "INSERT INTO `properties` (`KEY`, `VALUE`, `REMARK`) VALUES ('test_limit_price', '30.5', 'Limit Price')" ; MysqlConnectionPool *pool = MysqlConnectionPool::getInstance (); auto start_time = chrono::high_resolution_clock::now (); for (int i = 0 ; i < 1500 ; i++) { shared_ptr<MysqlConnection> connection = pool->getConnection (); connection->executeUpdate (insertSql.c_str ()); cout << "Insert " << i << " record, current pool size: " << pool->getSize () << endl; } auto end_time = chrono::high_resolution_clock::now (); chrono::duration<double , milli> elapsed_time = end_time - start_time; cout << "Total Times: " << elapsed_time.count () << "ms" << endl; delete pool; } void testConnectionPoolMultiThread () { const int num_threads = 15 ; thread threads[num_threads]; const string insertSql = "INSERT INTO `properties` (`KEY`, `VALUE`, `REMARK`) VALUES ('test_limit_price', '30.5', 'Limit Price')" ; MysqlConnectionPool *pool = MysqlConnectionPool::getInstance (); auto start_time = chrono::high_resolution_clock::now (); for (int i = 0 ; i < num_threads; i++) { threads[i] = thread ([&, i]() { for (int n = 0 ; n < 100 ; n++) { shared_ptr<MysqlConnection> connection = pool->getConnection (); connection->executeUpdate (insertSql.c_str ()); cout << "Thread " << i << ", current pool size: " << pool->getSize () << endl; } }); } for (int i = 0 ; i < num_threads; ++i) { threads[i].join (); } auto end_time = chrono::high_resolution_clock::now (); chrono::duration<double , milli> elapsed_time = end_time - start_time; cout << "Total Times: " << elapsed_time.count () << "ms" << endl; cout << "Waiting to collect idle connection..." << endl; sleep (15 ); cout << "Pool final size: " << pool->getSize () << endl; delete pool; } int main () { testConnectionPoolMultiThread (); char c = getchar (); return 0 ; }
1 2 3 4 5 6 7 8 cd c++-project-db-connection-poolcmake -S . -B build && cmake --build build ./bin/db_connection_pool_test
连接池的调试技巧 在开发数据库连接池项目的时候,会经常出现问题,也就是 MySQL API 调用出错,提示 Insert、Delete、Update 等操作执行失败,或者连接 MySQL Server 失败等,很多人不知道遇到这个问题该怎么办?
MySQL C API 调试 当使用的是 MySQL C API (Connector/C)库,可以使用以下两个函数打印出错时的提示信息:
函数 说明 int mysql_errno(MYSQL *)
返回上次调用的 MySQL 函数的错误编号。 const char* mysql_error(MYSQL *)
返回上次调用的 MySQL 函数的错误消息。
无论是 Insert 错误还是其它错误,都可以在代码上通过添加 mysql_error
函数打印错误提示信息(如下所示)。一般通过查看提示就可以知道是什么错误了,例如权限问题,但大部分都是细节错误,比如字段不对、类型不对、表名不对等。
MySQL Connector/C++ 调试 当使用的是 MySQL Connector/C++ (JDBC-Style API)库,可以通过异常来获取错误信息:
1 2 3 4 5 6 7 8 9 10 11 try { unique_ptr<sql::Connection> conn (driver->connect("tcp://127.0.0.1:3306" , "user" , "password" )) ; conn->setSchema ("test_db" ); unique_ptr<sql::Statement> stmt (conn->createStatement()) ; stmt->execute ("INVALID SQL STATEMENT" ); } catch (sql::SQLException &e) { cout << "Error Code: " << e.getErrorCode () << endl; cout << "SQL State: " << e.getSQLState () << endl; cout << "Message: " << e.what () << endl; }
通过 e.getErrorCode()
获取错误码,e.getSQLState()
获取 SQL 状态码,而 e.what()
获取详细的错误信息。
提示
如果使用的是 MySQL X DevAPI,错误提示信息同样可以通过异常处理来获取得到。
MySQL 的参数调整 以下命令可以查看和设置 MySQL Server 所支持的最大连接数,当超过 max_connections
数量的连接,MySQL Server 会直接拒绝。因此,在使用数据库连接池增加 MySQL 连接数量的时候,MySQL Server 的 max_connections
参数也要适当地进行调整,以适配数据库连接池的最大连接数(maxSize
),否则会大大影响数据库连接池的运行效果。
1 2 3 4 5 SHOW VARIABLES LIKE 'max_connections' ;SET GLOBAL max_connections = 200 ;
连接池的压力测试 验证数据库的插入操作所花费的时间,第一次测试使用普通的数据库访问操作,第二次测试使用带连接池的数据库访问操作,对比两次操作同样数据量所花费的时间,性能压力测试结果如下:
数据量 未使用连接池所花费时间 使用连接池所花费时间 1000 单线程:1891ms,四线程:497ms 单线程:1079ms,四线程:408ms 5000 单线程:10033ms,四线程:2361ms 单线程: 5380ms,四线程:2041ms 10000 单线程:19403ms,四线程:4589ms 单线程:10522ms,四线程:4034ms
连接池的代码下载 数据库连接池项目的完整代码(包括 MySQL Connector/C++ 的头文件和动态链接库)可以从 这里 下载得到,由于笔者的技术水平有限,暂时无法保证代码没有潜在的 Bug,因此所有 C++ 代码仅供学习参考。 参考资料