【MySQL】访问 & 数据流动原理

🌈欢迎来到MySQL专栏

MySQL

🌏链接上数据库

cpp 复制代码
#include <iostream>
#include <mysql/mysql.h>

int main()
{
    std::cout << "mysql client version: " << mysql_get_client_info() << std::endl;
    return 0;
}
cpp 复制代码
g++ test.cc -o test -L/lib64/mysql -lmysqlclient;

其中 include 包含所有的方法声明 , lib 包含所有的方法实现(打包成库)

  • lmysqlclient:告诉要链接哪个库
  • -L/lib64/mysql:告诉去哪里能找到这个库
  • 头文件,在include中能搜索的到:mysql/mysql.h;如果不加就要 -I去手动链接
cpp 复制代码
./test 
mysql client version: 5.7.44

🌏MySQL接口介绍

要使用库,必须先进行初始化!

初始化:mysql_init(),最后不用记得关闭mysql_close()

cpp 复制代码
MYSQL *my = mysql_init(nullptr);

初始化完毕之后,必须先链接数据库,在进行后续操作。(mysql网络部分是基于TCP/IP的)

cpp 复制代码
MYSQL *mysql_real_connect(MYSQL *mysql, 
	const char *host,
	const char *user,
	const char *passwd,
	const char *db,
	unsigned int port,
	const char *unix_socket,
	unsigned long clientflag
);
cpp 复制代码
#include <iostream>
#include <string>
#include <mysql/mysql.h>

const std::string host = "127.0.0.1";
const std::string user = "connector";
const std::string password = "Ljj@747501";
const std::string db = "conn";
const unsigned int port = 3306;

int main()
{
    MYSQL *my = mysql_init(nullptr);
    if(my == nullptr)
    {
        std::cerr << "init MySQL error" << std::endl;
        return 1;
    }

    if(mysql_real_connect(my, host.c_str(), user.c_str(), password.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
    {
        std::cerr << "Connect MySQL error" << std::endl;
        return 2;
    }

    std::cout << "Connect MySQL success" << std::endl;

    mysql_close(my);
    return 0;
}

链接成功~

在conn库中创建出一个表

sql 复制代码
mysql> create table user(
    -> id bigint primary key auto_increment,
    -> name varchar(20) not null,
    -> age int not null,
    -> telphone varchar(32) unique
    -> );

紧接着下发mysql命令mysql_query

sql 复制代码
int mysql_query(MYSQL *mysql, const char *q);
  • 第一个参数是初始化后的数据库名
  • 第二个参数为要执行的sql语句
cpp 复制代码
std::string sql = "update user set name='sheriy' where id = 1";
int n = mysql_query(my, sql.c_str());

if(n == 0) std::cout << sql << "success" << n << std::endl;
else std::cout << sql << "failed" << n << std::endl;
sql 复制代码
mysql> select * from user;
+----+------+-----+----------+
| id | name | age | telphone |
+----+------+-----+----------+
|  1 | tom  |  18 | 123456   |
+----+------+-----+----------+
⬇️
mysql> select * from user;
+----+--------+-----+----------+
| id | name   | age | telphone |
+----+--------+-----+----------+
|  1 | sheriy |  18 | 123456   |
+----+--------+-----+----------+

那么如果我们插入中文字段呢? 会出现乱码问题!

cpp 复制代码
std::string sql = insert into user (name, age, telphone) values ('张三', 18, '123456');
sql 复制代码
mysql> select * from user;
+----+---------------+-----+----------+
| id | name          | age | telphone |
+----+---------------+-----+----------+
|  1 | sheriy        |  18 | 123456   |
|  2 | å¼ ä¸‰        |  18 | 12342256 |
+----+---------------+-----+----------+

建立好链接之后,获取英文没有问题,如果获取中文是乱码

  • 设置链接的默认字符集是utf8,原始默认是latin1
cpp 复制代码
mysql_set_character_set(myfd, "utf8");

设置完后就能正常显示了!

sql 复制代码
mysql> select * from user;
+----+---------------+-----+--------------+
| id | name          | age | telphone     |
+----+---------------+-----+--------------+
|  1 | sheriy        |  18 | 123456       |
|  2 | å¼ ä¸‰        |  18 | 12342256     |
|  4 | 张三          |  18 | 123427878256 |
+----+---------------+-----+--------------+

🔥C++ 查询处理细节

当我们进行查询后,我们来看看如何获取查询结果?实际上执行结果会保存在my这个句柄里的(包括了保留数据的缓冲区)

如果mysql_query返回成功,那么我们就通过mysql_store_result这个函数来读取结果

原型如下:

cpp 复制代码
MYSQL_RES *mysql_store_result(MYSQL *mysql);

该函数会调用MYSQL变量中的st_mysql_methods中的 read_rows 函数指针来获取查询的结果。同时该函数会返回MYSQL_RES 这样一个变量,该变量主要用于保存查询的结果 。同时该函数malloc了一片内存空间来存储查询过来的数据,所以我们一定要记的 free(result),不然是肯定会造成内存泄漏的 。 执行完mysql_store_result以后,其实数据都已经在MYSQL_RES 变量中了

cpp 复制代码
mysql_free_result(res);

把结果转储到 MYSQL_RES 结构体中

sql 复制代码
MYSQL_RES * res = mysql_store_result(my);
if(nullptr == res)
{
    std::cout << sql << "mysql_store_result failed!" << n << std::endl;
    return 4;
}

首先理解一下表结构,其中竖列是表表的属性,其他就是表的内容

我们的数据不已经放在了my结构体里了吗?为什么还需要进行转储呢?

  • 数据在 my 结构体里:只说明它在当前进程/当前运行时的内存里有一份表示,不等于它已经被安全保存或可被外部使用 。是否需要转储(dump/序列化/持久化)取决于你的目的

首先MySQL将所有的数据,读取出来的时候,全部都当作字符串看待

下面的api基本就是读取MYSQL_RES 中的数据

获取结果行数:

c 复制代码
my_ulonglong mysql_num_rows(MYSQL_RES *res);

获取结果列数:

sql 复制代码
unsigned int mysql_num_fields(MYSQL_RES *res);
sql 复制代码
select * from usersuccess0
行:2
列:4

那如果想要获取结果内容呢?mysql_fetch_row

  • 它会返回一个变量,MYSQL_ROW其实就是char **,通过确定访问哪一行,再进而确定哪一列!
cpp 复制代码
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
cpp 复制代码
for(int i = 0; i < rows; i++)
{
    //类似于迭代器! 逐行取出
    MYSQL_ROW row = mysql_fetch_row(res);
    for(int j = 0; j < fields; j++)
    {
        std::cout << row[j] << "\t"; //遍历行的每一个
    }
    std::cout << "\n";
}

获取列名 :mysql_fetch_fields

cpp 复制代码
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);
cpp 复制代码
MYSQL_FIELD *fields_array = mysql_fetch_fields(res);
for(int i = 0; i < fields; i++)
{
    std::cout << fields_array[i].name << "\t";
}
std::cout << "\n";

//✅️
id      name    age     telphone
1       sheriy  18      123456
4       张三    18      123427878256

最终版代码

cpp 复制代码
#include <iostream>
#include <string>
#include <mysql/mysql.h>

const std::string host = "127.0.0.1";
const std::string user = "connector";
const std::string password = "Ljj@747501";
const std::string db = "conn";
const unsigned int port = 3306;

int main()
{
    MYSQL *my = mysql_init(nullptr);
    if(my == nullptr)
    {
        std::cerr << "init MySQL error" << std::endl;
        return 1;
    }

    if(mysql_real_connect(my, host.c_str(), user.c_str(), password.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
    {
        std::cerr << "Connect MySQL error" << std::endl;
        return 2;
    }
    //设置链接的默认字符集是utf8
    mysql_set_character_set(my, "utf8");

    std::string sql = "select * from user";

    int n = mysql_query(my, sql.c_str());
    if(n == 0) std::cout << sql << "success" << n << std::endl;
    else 
    {
        std::cout << sql << "failed" << n << std::endl;
        return 3;
    }
    MYSQL_RES * res = mysql_store_result(my);
    if(nullptr == res)
    {
        std::cout << sql << "mysql_store_result failed!" << n << std::endl;
        return 4;
    }
    
    //全部都是和结果集res有关!
    int rows = mysql_num_rows(res); //获取行数
    int fields = mysql_num_fields(res); //获取列数

    std::cout << "行:" << rows << std::endl;
    std::cout << "列:" << fields << std::endl;

    //属性
    MYSQL_FIELD *fields_array = mysql_fetch_fields(res);
    for(int i = 0; i < fields; i++)
    {
        std::cout << fields_array[i].name << "\t";
    }
    std::cout << "\n";

    //内容
    for(int i = 0; i < rows; i++)
    {
        //类似于迭代器! 逐行取出
        MYSQL_ROW row = mysql_fetch_row(res);
        for(int j = 0; j < fields; j++)
        {
            std::cout << row[j] << "\t"; //遍历行的每一个
        }
        std::cout << "\n";
    }
    
    mysql_free_result(res);
    mysql_close(my);
    return 0;
}

🌏连接池原理

在创建一批线程的同时 + 创建出MySQL对象:也就是每个线程分配一个MySQL对象

  • 让多个线程预先和MySQL先建立好链接
  • 后续有任务到来,就push到连接池里
  • 在进入while死循环之前,先让MySQL先链接上 ------ 再进入到对应的事件循环中
  • 那么每个线程都有自己的MySQL的句柄,并且是链接好的
cpp 复制代码
class task
{
private:
    std::string sql;        // 要执行的SQL语句
    std::function<void(Result)> cb;  // 回调函数
};
  • sql:告诉线程池需要执行什么SQL

  • cb:告诉线程池SQL执行完毕后,应该如何处理结果

线程池创建工作线程时,就为每个工作线程创建一个独立的 MySQL 对象,并提前调用 mysql_real_connect() 建立数据库连接,连接成功后再进入 while(true) 事件循环。此后,工作线程不断从任务队列中取出任务,使用自己持有的 MySQL 连接执行 SQL,执行完成后通过任务中封装的回调函数 cb(res) 将结果返回给业务逻辑,然后继续处理下一个任务。这样每个线程都能复用自己的数据库连接,避免频繁创建和销毁连接,提高数据库访问效率。

🌏简易网站数据流动如何进行?

比如说一个用户逻辑是怎么走的? 注册or登录

  • 用户 ------ 服务器&&网站 ------ 中间件 ------ 数据库

Web业务处理 + C++数据库负载均衡 + MySQL集群定时同步

  • 用户访问网站:浏览器向服务器发送 HTTP GET 请求,获取注册页面或登录页面。

  • 用户提交信息:用户填写用户名、密码等信息,通过 HTTP POST 请求将数据发送给服务器。

  • Web应用层处理 :Nginx / Tomcat 接收请求,由 PHP、Python 或 Java 等应用程序提取 GET / POST 参数 ,完成业务逻辑处理,并生成对应的 SQL 请求。

  • C++负载均衡:应用层将数据库请求交给 C++ 模块,由 C++ 根据负载均衡策略,从三个 MySQL 节点中选择一个节点,将 SQL 请求转发过去。

  • MySQL执行操作:被选中的 MySQL 节点执行 INSERT、SELECT、UPDATE 等操作,并将执行结果返回给 C++ 模块。

  • 结果返回用户:C++ 将数据库执行结果返回给 Web 应用层,应用层进一步处理后,通过 HTTP 响应返回浏览器。

  • MySQL数据同步:三个 MySQL 节点之间按照设定的周期进行数据同步,使各节点的数据保持一致

用户通过浏览器发起 HTTP 请求,Web应用层负责接收请求和处理业务逻辑。对于数据库查询,优先访问缓存,命中则直接返回,未命中则通过 C++ 负载均衡模块选择一个 MySQL 节点执行查询,并将结果写入缓存。对于数据写入或修改,则选择一个 MySQL 节点执行操作,并处理相关缓存更新。三个 MySQL 节点之间通过定时同步机制共享数据

📢写在最后

接下来登场的是 ??

相关推荐
朱涛的自习室3 小时前
212 个需求、22 万行代码:Agent 接管开源项目的 19 天
android·前端·人工智能
这个DBA有点耶4 小时前
信创场景下数据同步软件怎么选?对比开源CDC与原生闭环工具的结构性差异
数据库·mysql·架构
多客:duokelijie5 小时前
代练订单系统选型思路:跨品类游戏接入、订单分层管理、自动结算逻辑拆解!
数据库·mysql·微信小程序·前端框架·uni-app·php·持续部署
PingCAP5 小时前
平凯星辰上榜!2026 年胡润全球独角兽企业
数据库
PingCAP5 小时前
轨道交通 AFC 系统升级,平凯 Loop 把跨部门协同变成可追溯闭环
数据库
java_logo6 小时前
Docker 部署 CloudBeaver:轻松搭建网页数据库管理平台
数据库·docker·容器·数据库管理·dbeaver·轩辕镜像·cloudbeaver
李白客7 小时前
AI 数据库落地 NL2SQL:准确率卡在四成,问题在表注释不在模型
数据库
FluxDB7 小时前
FluxDB vs DBX vs DBeaver:GPUI、Tauri、Eclipse 三种技术栈实测
数据库·mysql