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

news2024/11/18 17:28:56

目录

☀️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 method
    QueryResultFuture 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.coloradmin.cn/o/2132962.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring 循环依赖原理及解决方案

一、什么是循环依赖 循环依赖指的是一个实例或多个实例存在相互依赖的关系&#xff08;类之间循环嵌套引用&#xff09;。 举例&#xff1a; Component public class AService {// A中注入了BAutowiredprivate BService bService; }Component public class BService {// B中也…

通信工程学习:什么是PC永久连接、SPC软永久连接

一、PC永久连接 PC&#xff08;Permanent Connection&#xff09;永久连接是一种由网管系统通过网管协议建立的长期稳定的连接方式。在ASON&#xff08;自动交换光网络&#xff09;中&#xff0c;PC永久连接沿袭了传统光网络的连接建立形式&#xff0c;其特点主要包括&#xff…

【Canvas与密铺】正六边形、正方形和正三角形的密铺

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>正六边形正方形和正三角形的密铺1920x1080</title><style t…

SpringSecurity原理解析(四):SpringSecurity初始化过程

1、对 SpringSecurity初始化时的几个疑问 通过对前边一个请求流转的分析&#xff0c;我们知道一个请求要想到达服务端Servlet需要经过n多个 拦截器处理&#xff0c;请求处理流程如下所示&#xff1a; 对于一个请求到来后会通过FilterChainProxy来匹配一个对应的过滤器链来处理该…

PEST分析法包括哪些内容?用在线白板工具轻松绘制,简单好用!

在当今瞬息万变的商业环境中&#xff0c;企业需要一个全面而系统的方法来分析外部环境对其经营的影响。PEST分析模型恰好提供了这样一个强大的工具&#xff0c;帮助企业洞察政治、经济、社会和技术因素对其发展的潜在影响。 然而&#xff0c;如何高效地创建PEST分析模型一直是…

Unity 第一人称游戏的武器被其他物体覆盖解决方案

在第一人称游戏的时候&#xff0c;会出现渲染过程中&#xff0c;主角的手持武器可能会被其他物体挡住。 解决方法 在主摄像机下再创建一个摄像机&#xff0c;负责渲染不同图层 Main Camera的参数&#xff1a;我们这个摄像机不渲染equipable层&#xff08;自定义武器为equipab…

前后端分离项目实现SSE

SSE介绍 在日常web开发中经常会遇到查看数据最新状态的业务场景&#xff0c;例如查看任务状态与日志内容等。比较场景的解决方案是轮循和SSE。 Server-Sent Events (SSE) 是一种允许服务器通过单向通道向客户端推送更新的技术。它基于HTTP协议&#xff0c;客户端使用一个标准…

2024CCPC网络预选赛

vp链接&#xff1a;Dashboard - The 2024 CCPC Online Contest - Codeforces B. 军训 II 序列 a 从小到大排列或者从大到小排列时&#xff0c;不整齐度是最小的。方案数是所有相同数字的个数的排列数的乘积。如果首尾的数字不同的话&#xff0c;还要再乘个 2。 #include <…

Running setup.py install for wxPython did not run successfully.

Running setup.py install for wxPython did not run successfully. 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市开…

axure循环介绍

一直在犹豫要不要写关于axure循环方面的介绍&#xff0c;因为循环的场景用其它方法都是可以实现的&#xff0c;今天还是用上次手机号码判断的案例来写一下循坏吧。 1、页面新建元件&#xff0c;手机号码输入框重命名为【手机号码输入框】按钮重命名为【按钮】再在页面拖动上来一…

python学习第八节:爬虫的初级理解

python学习第八节&#xff1a;爬虫的初级理解 爬虫说明&#xff1a;爬虫准备工作&#xff1a;分析网站url分析网页内容 爬虫获取数据&#xff1a;1.使用urllib库发起一个get请求2.使用urllib库发起一个post请求3.网页超时处理4.简单反爬虫绕过5.获取响应参数6.完整请求代码 解析…

【Python机器学习】长短期记忆网络(LSTM)

目录 随时间反向传播 实践 模型的使用 脏数据 “未知”词条的处理 字符级建模&#xff08;英文&#xff09; 生成聊天文章 进一步生成文本 文本生成的问题&#xff1a;内容不受控 其他记忆机制 更深的网络 尽管在序列数据中&#xff0c;循环神经网络为对各种语言关系…

Java项目: 基于SpringBoot+mybatis+maven医院管理系统(含源码+数据库+任务书+开题报告+毕业论文)

一、项目简介 本项目是一套基于SpringBootmybatismaven医院管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、…

强化网络安全:通过802.1X协议保障远程接入设备安全认证

随着远程办公和移动设备的普及&#xff0c;企业网络面临着前所未有的安全挑战。为了确保网络的安全性&#xff0c;同时提供无缝的用户体验&#xff0c;我们的 ASP 身份认证平台引入了先进的 802.1X 认证协议&#xff0c;确保只有经过认证的设备才能接入您的网络。本文档将详细介…

【我的 PWN 学习手札】Fastbin Attack

关于fastbin&#xff0c;有很多攻击利用手法&#xff0c;本篇只是讲述了修改fd指针&#xff0c;分配到fake_chunk&#xff0c;更多利用手法拆分到后面的博客中 目录 前言 一、Fastbin保护检查机制 二、利用手法 &#xff08;1&#xff09;分配到任意地址&#xff08;__mall…

VScode相关问题与解决

1.写c文件时找不到头文件stdio.h 在linux下我们gcc命令来编译c文件时&#xff0c;会遇到找不到头文件的问题 解决方法&#xff1a;我们每写完一个文件记得保存一下文件即可&#xff0c;这样就解决了找不到头文件的问题&#xff01; 参考链接&#xff1a; /usr/bin/ld: /us…

Java实现生成验证码实战

文章目录 需求描述思想思路实现代码实现效果 在实际项目中&#xff0c;管理端的登录&#xff0c;会涉及验证码的校验&#xff0c;简单的数字与字母组合形式&#xff0c;在Java中要如何生成与实现&#xff0c;记录下来&#xff0c;方便备查。 需求描述 生成8位的由数字、大写字…

总结拓展九:SAP数据迁移(2)

第三节 数据迁移工具LTMC实操 1、供应商&#xff08;BP&#xff09;主数据导入 1.1 首先在SAP S 4系统&#xff0c;通过事务代码“LTMC”跳转进入数据迁移控制台&#xff08;网页版&#xff09;&#xff1b; 1.2 点击“创建”按钮&#xff0c;创建迁移项目“NJDHMM-01”; 传…

AI问答-Vue实例属性/实例方法:$refs、$emit、$attrs、$props、$data...

一、本文简介 在Vue.js中&#xff0c;$ 符号通常用于表示Vue实例或组件上的内置属性和方法&#xff0c;这些被称为“实例属性”或“实例方法”。以下是一些常见的以$开头的Vue实例属性和方法 1.1、实例属性 序号实例属性解释1$dataVue实例的数据对象&#xff0c;用于存储组件…

Java铸基之路:运算符的深入学习!(上)

&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d;&#x1f51d; &#x1f947;博主昵称&#xff1a;小菜元 &#x1f35f;博客主页…