剖析 MySQL 数据库连接池(C++版)

目录

☀️0. 前言

🌤️1. 数据库连接池概述

⛅1.1 服务器与数据库交互

⛅1.2 MySQL 数据库网络模型

⛅1.3 MySQL 连接驱动安装

⛅1.4 同步(synchronous)连接池与异步(asynchronous)连接池

⛅1.5 同步连接池和异步连接池的使用场景

⛅1.6 实现同步连接池与异步连接池

🌥️1.6.1 实现同步连接池

🌥️1.6.2 实现异步连接池

🌤️2. 实现数据库连接池

⛅2.1 初始化数据库连接池连接信息

⛅2.2 SQL 字符串与预处理的 SQL 语句

⛅2.3 函数接口封装和调用关系

🌤️3. 结束语


☀️0. 前言

上次给大家介绍了线程池的实现,这次来介绍下 Linux 下数据库连接池的实现,以大家最为熟悉的 MySQL 为例,因为连接池总体实现还是较为复杂的,本次以一个开源框架的数据库连接池部分为例进行讲解。

🌤️1. 数据库连接池概述

⛅1.1 服务器与数据库交互

首先,服务器与数据库的交互是请求响应模式,通常所使用的是 TCP 长连接,而 TCP 连接需要三次握手和四次挥手,并且每次连接都需要验证账号密码等,所以连接资源属于耗时资源,可以用连接池来复用连接。

⛅1.2 MySQL 数据库网络模型

这里来简单介绍以下 MySQL 数据库的网络模型:

主线程使用 IO 多路复用中的 select 来监听 listenfd,如果 listenfd 上触发了可读事件,就说明有客户端来连接,就为他分配一个连接线程,同一个连接上,如果收到多个请求,该连接线程是串行执行的。如果对 IO 多路复用部分不熟悉的同学,可以看看我网络专栏的部分文章,这里你或许会疑惑为什么 MySQL 用的是 select,而不是性能更高的 epoll?主要原因是 select 支持跨平台,epoll 只是 linux 下的,并且 MySQL 规定最高只能监听128个文件描述符,在这种小数量下,select 其实和epoll 性能是差不多的。

所以当我们创建多条连接时,每条连接 MySQL 都会分配一个线程,可以充分释放 MySQL 执行SQL 语句的性能,那是不是创建的连接越多越好呢?显然不是的,看过线程池的同学应该明白,一般数量控制在 CPU 核心数比较合适,创建的太多反而会降低性能。

⛅1.3 MySQL 连接驱动安装

如果要用 C/C++ 实现数据库连接池,首先要安装 libmysqlclient-dev 驱动,这个官方提供的驱动使用了阻塞 IO,具体这个阻塞的 IO 要怎么理解呢?以最常见的 query() 函数为例:首先将 sql 通过MySQL 协议打包,然后服务器调用 send() 或 write(),然后调用 rend() 或 recv() 会阻塞线程等待 MySQL 的返回,最后确定是完整回应包后进行协议解析并将结果返回,协议打包和解析都是MySQL 驱动帮我们完成的,所以可以很明显看到这是个耗时操作,一般主线程在处理业务逻辑需要和数据库交互的时候,这种阻塞的耗时操作都需要优化。

⛅1.4 同步(synchronous)连接池与异步(asynchronous)连接池

同步和异步的概念不好理解,经常要和阻塞和非阻塞搞混,这里以后面要实现的两个接口为例:

QueryResult Query(char const* sql, T* connection = nullptr);
QueryCallback AsyncQuery(char const* sql);

第一个Query()是同步连接池的接口,另一个AsyncQuery()是异步连接池的接口,同步和异步的区别就在于执行一条 SQL 语句后,怎么拿到数据库的返回值,在同步连接池中,Query() 一返回,就可以拿到数据库的返回值,而在异步连接池中,执行 SQL 并不是发生在 AsyncQuery() 中,AsyncQuery() 返回的结果也不是数据库的返回值,当前的职责是在其他接口中实现的。

这里要区分阻塞与同步,非阻塞与异步的区别,阻塞是指的线程或者协程,在等待某个事件时无法继续工作;同步是指的任务按顺序执行,一个完成后才能执行下一个;同样非阻塞和异步也是同样的区别,阻塞是实现同步的一种方式,但不是唯一的,感兴趣的同学可以去了解一下非阻塞同步编程方式。

给大家看一下两个接口的具体实现函数:

QueryResult DatabaseWorkerPool<T>::Query(char const* sql, T* connection /*= nullptr*/)
{if (!connection)connection = GetFreeConnection();ResultSet* result = connection->Query(sql);connection->Unlock();if (!result || !result->GetRowCount() || !result->NextRow()){delete result;return QueryResult(nullptr);}return QueryResult(result);
}

其中 Query(char const* sql, T* connection /*= nullptr*/) 调用了 Query(char const* sql),Query(char const* sql) 又调用了 _Query(const char* sql, MySQLResult** pResult, MySQLField** pFields, uint64* pRowCount, uint32* pFieldCount),_Query(const char* sql, MySQLResult** pResult, MySQLField** pFields, uint64* pRowCount, uint32* pFieldCount) 中调用了 mysql_query()这个数据库提供的阻塞函数。所以该函数最终返回的结果就是数据库的返回值。

再来看看异步连接的实现:

QueryCallback DatabaseWorkerPool<T>::AsyncQuery(char const* sql)
{BasicStatementTask* task = new BasicStatementTask(sql, true);// Store future result before enqueueing - task might get already processed and deleted before returning from this methodQueryResultFuture result = task->GetFuture();Enqueue(task);return QueryCallback(std::move(result));
}

可以看到是把任务放到了一个队列中,实际上是交给了线程池来做,不阻塞当前线程,去阻塞线程池中的一个线程来获取数据库的返回值。

⛅1.5 同步连接池和异步连接池的使用场景

异步连接池的性能要高于同步连接池,那什么时候适合使用同步连接池呢?在服务器初始化阶段,因为这一阶段许多操作是线性、依赖关系明确的,如初始化数据库连接、加载配置、启动服务等,这些操作通常需要按照严格的顺序执行,确保前面的步骤完成后,后续步骤才能安全进行。如果使用异步机制,虽然可以提升并发性能,但可能会引入复杂的错误处理逻辑,增加调试的难度;所以服务器启动过程的可靠性优先于性能,并且短暂的初始化过程中也没有大量并发需求,而同步正好可以让任务顺序执行,适合这一阶段。初次之外,在处理业务时都适合用异步连接池。

⛅1.6 实现同步连接池与异步连接池

🌥️1.6.1 实现同步连接池

示意图如上所示,开启多个线程加快服务器初始化,加锁的目的是为了不让多个线程同时使用一个连接,同时是否上锁也是该连接是否空闲的标志。获取连接的方式是round robin(轮询算法),依次查看连接是否空闲,发现空闲连接就上锁,与数据库进行交互,阻塞该线程等待数据库返回,返回之后释放锁。

🌥️1.6.2 实现异步连接池

示意图如上所示,基于线程池(对线程池不熟悉的同学,可以去看我另一篇实现线程池的文章)实现,用户请求 push 进 SQL 任务执行队列,让线程池中的线程去取任务并与数据库交互,从而实现不阻塞主线程,去阻塞线程池中的线程,与传统的线程池不同的是,一般线程池中线程会因为任务队列为空而阻塞,异步连接池中的线程阻塞除了上述原因外,还可能是为了等待数据库返回而阻塞。线程池中的连接是线程安全的,每个连接都和特定的线程一一绑定,而具体的任务与连接是无关的。

🌤️2. 实现数据库连接池

代码来自于一个名为 TrinityCore 的项目,该项目是一个开源的 MMORPG 服务端模拟器,重点来分析该项目中数据库连接池的设计,主要剖析的主文件是DatabaseWorkerPool.h、DatabaseWorkerPool.cpp。

首先要明确一个概念,MySQL 中很多数据库,一个连接池对应着一个库,如果需要访问两个库,就需要两个连接池,以此类推。

⛅2.1 初始化数据库连接池连接信息

相关函数和类定义如下:

// 设置数据库连接信息,包括数据库连接字符串和异步、同步线程的数量
void SetConnectionInfo(std::string const& infoString, uint8 const asyncThreads, uint8 const synchThreads);void DatabaseWorkerPool<T>::SetConnectionInfo(std::string const& infoString, uint8 const asyncThreads, uint8 const synchThreads)
{_connectionInfo = std::make_unique<MySQLConnectionInfo>(infoString);_async_threads = asyncThreads;_synch_threads = synchThreads;
}struct TC_DATABASE_API MySQLConnectionInfo
{explicit MySQLConnectionInfo(std::string const& infoString);std::string user;std::string password;std::string database;std::string host;std::string port_or_socket;std::string ssl;
};

可以看到首先初始化数据库连接信息(包括用户名、密码、哪个库等),然后设置同步和异步的线程数量。

⛅2.2 SQL 字符串与预处理的 SQL 语句

void Execute(char const* sql); // 简单字符串,如“select * from table;”
void Execute(PreparedStatement<T>* stmt); // 预处理好的SQL语句

这里简单的说一下预处理,预处理是指在执行 SQL 查询之前,将 SQL 语句的结构进行预编译和缓存,以便多次执行,提高效率并增强安全性。具体来说,预处理可以提高执行效率,对于重复执行的 SQL 语句,预处理能够提高执行效率。SQL 语句只需要编译一次,以后每次执行时只需要传递参数并执行,节省了重新编译的开销。且预处理可以防止 SQL 注入,预处理可以有效防止 SQL 注入攻击。因为用户提供的输入是作为参数绑定到预编译的 SQL 语句中的,攻击者无法通过输入恶意的 SQL 代码来篡改查询逻辑。例如,如果使用预处理,攻击者不能通过输入 1 OR 1=1 来破坏查询逻辑,因为这会被视为一个普通的字符串参数,而不是 SQL 代码。

⛅2.3 函数接口封装和调用关系

上图为总体的核心的函数调用关系,大家只要对线程池有充分的理解,就可以很容易看懂这里面的调用关系。源代码比较多,但是核心的部分就在这里面,代码虽多但很好理解,完整代码我放在了数据库连接池源代码,感兴趣的同学可以去自己阅读以下

🌤️3. 结束语

本文较为简单的阐述了数据库连接池的实现方法,本人目前还是个在校生,还比较小白,也刚刚开始写 CSDN 博客不久,可能写的也不是很好,如果有任何疑问或者发现我有哪里写的不对的地方,欢迎大家留言告诉我!我都会一一改正的。

如果觉得文章对你有帮助的话,还请点赞,关注,收藏支持小占!

更多资源可查看:资源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/426335.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

记录开发一个英语听力训练网站

背景 在当前全球经济衰退的背景下&#xff0c;IT相关的工作在国内的竞争也是越来越激烈&#xff0c;为了能够获得更多的可能性&#xff0c;英语的学习也许能为程序员打开一扇新的窗户&#xff0c;比如很多远程的工作尤其是国际化背景的工作团队&#xff0c;英语的协作沟通是必…

yolov8-obb中存在的一个bug

yolov8支持OBB目标检测,且能提供较好的性能。 但是最近在使用yolov8-obb的过程中,发现yolov8-obb存在一个bug。即训练数据如果包含不带旋转角度的水平目标时,训练出的模型,经常会输出垂直的检测框,需要旋转90度以后才能得到最终结果。把yolov8-obb相关的源码阅读一遍才发…

初始爬虫5

响应码&#xff1a; 数据处理&#xff1a; re模块&#xff08;正则表达式&#xff09; re模块是Python中用于正则表达式操作的标准库。它提供了一些功能强大的方法来执行模式匹配和文本处理。以下是re模块的一些常见用法及其详细说明&#xff1a; 1. 基本用法 1.1 匹配模式 …

STM32 的 RTC(实时时钟)详解

目录 一、引言 二、RTC 概述 三、RTC 的工作原理 1.时钟源 2.计数器 3.闹钟功能 4.备份寄存器 四、RTC 寄存器 1.RTC_TR&#xff08;Time Register&#xff0c;时间寄存器&#xff09; 2.RTC_DR&#xff08;Date Register&#xff0c;日期寄存器&#xff09; 3.RTC_S…

TCP 拥塞控制:一场网络数据的交通故事

从前有条“高速公路”&#xff0c;我们叫它互联网&#xff0c;而这条公路上的车辆&#xff0c;则是数据包。你可以把 TCP&#xff08;传输控制协议&#xff09;想象成一位交通警察&#xff0c;负责管理这些车辆的行驶速度&#xff0c;以防止交通堵塞——也就是网络拥塞。 第一…

【MPC】无人机模型预测控制复现Data-Driven MPC for Quadrotors项目(Part 1)

无人机模型预测控制复现Data-Driven MPC for Quadrotors项目 参考链接背景和问题方法与贡献实验结果安装ROS创建工作空间下载RotorS仿真器源码和依赖创建Python虚拟环境下载data_driven_mpc仓库代码下载并配置ACADO求解器下载并配置ACADO求解器的Python接口下载并配置rpg_quadr…

基于密码的大模型安全治理的思考

文章目录 前言一、大模型发展现状1.1 大模型技术的发展历程1.2 大模型技术的产业发展二、大模型安全政策与标准现状2.1 国外大模型安全政策与标准2.2 我国大模型安全政策与标准前言 随着大模型技术的迅速发展和广泛应用,其安全性问题日益凸显。密码学作为网络空间安全的核心技…

如何简化机器人模型,加速仿真计算与可视化

通常,我们希望将自己设计的机器人模型导入仿真环境。由于是通过 CAD 软件设计的,导出的 urdf 使用 STL 或 DAE 文件来表示 3D 几何。但原始的 STL 或 DAE 文件通常过于复杂(由数十万个三角面片组成),这会减慢仿真速度,有时也会导致仿真软件报错(如Webots)。为了在正确描述…

【Linux】调试和Git及进度条实现

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;Linux入门到进阶 &#x1f30f;代码仓库&#xff1a; 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经验和讨论 目录 1.…

KVM创建的虚拟机无法访问外网

基础环境如下&#xff1a; [rootlocalhost ~]# virsh domifaddr CentOS7_YFName MAC address Protocol Address -------------------------------------------------------------------------------vnet0 52:54:00:cb:a6:0d ipv4 192.168.…

Java中的事务管理

1.1 事务管理 1.1 事务回顾 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位。事务会把所有的操作作为一个整体&#xff0c;一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功&#xff0c;要么同时失败。 怎么样来控制这组操作&#xff0c;让这组操…

OpenCV高阶操作

在图像处理与计算机视觉领域&#xff0c;OpenCV&#xff08;Open Source Computer Vision Library&#xff09;无疑是最为强大且广泛使用的工具之一。从基础的图像读取、 1.图片的上下&#xff0c;采样 下采样&#xff08;Downsampling&#xff09; 下采样通常用于减小图像的…

RabbitMQ(高阶使用)延时任务

文章内容是学习过程中的知识总结&#xff0c;如有纰漏&#xff0c;欢迎指正 文章目录 1. 什么是延时任务&#xff1f; 1.1 和定时任务区别 2. 延时队列使用场景 3. 常见方案 3.1 数据库轮询 优点 缺点 3.2 JDK的延迟队列 优点 缺点 3.3 netty时间轮算法 优点 缺点 3.4 使用消息…

安卓BLE蓝牙通讯

蓝牙测试demo 简介   Android手机间通过蓝牙方式进行通信&#xff0c;有两种常见的方式&#xff0c;一种是socket方式&#xff08;传统蓝牙&#xff09;&#xff0c;另一种是通过GATT&#xff08;BLE蓝牙&#xff09;。与传统蓝牙相比&#xff0c;BLE 旨在大幅降低功耗。这样…

【Obsidian】当笔记接入AI,Copilot插件推荐

当笔记接入AI&#xff0c;Copilot插件推荐 自己的知识库笔记如果增加AI功能会怎样&#xff1f;AI的回答完全基于你自己的知识库余料&#xff0c;是不是很有趣。在插件库中有Copilot插件这款插件&#xff0c;可以实现这个梦想。 一、什么是Copilot&#xff1f; 我们知道githu…

香橙派zero2w上手——环境配置添加OLED小屏幕

0 硬件参数 origin pi zero2W 硬件参数 CPU全志 H618 四核 64 位 1.5GHz Cortex-A53 处理器GPUMali G31 MP2&#xff0c;支持OpenGL ES 1.0/2.0/3.2&#xff0c;OpenCL 2.0&#xff0c;Vulkan 1.1内存LPDDR4:1GB/1.5GB/2GB/4GB (可选)存储SPI Flash: 16MBWiFi蓝牙WiFi蓝牙二合…

将硬盘的GPT 转化为MBR格式

遇到的问题 在重新安装系统时&#xff0c;磁盘遇到无法空间分配给系统。 解决方式 使用Windows10镜像 U盘安装&#xff0c;选择磁盘时&#xff0c;转换磁盘格式为MBR。然后退出安装程序。 Shift F10# 输入 diskpart# 查看磁盘信息 list disk# 选择需要转换的磁盘&#xff0…

【网络安全的神秘世界】攻防环境搭建及漏洞原理学习

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 Kali安装docker 安装教程 PHP攻防环境搭建 中间件 介于应用系统和系统软件之间的软件。 能为多种应用程序合作互通、资源…

一、机器学习算法与实践_02KNN算法笔记

1、KNN基本介绍 1.1 定义 KNN&#xff08;K-NearestNeighbor&#xff0c;即&#xff1a;K最邻近算法&#xff09;是一种基于实例的学习方法&#xff0c;用于分类和回归任务&#xff0c;它通过查找一个数据点的最近邻居来预测该数据点的标签或数值。 所谓K最近邻&#xff0c;…

Golang | Leetcode Golang题解之第402题移掉K位数字

题目&#xff1a; 题解&#xff1a; func removeKdigits(num string, k int) string {stack : []byte{}for i : range num {digit : num[i]for k > 0 && len(stack) > 0 && digit < stack[len(stack)-1] {stack stack[:len(stack)-1]k--}stack app…