mysql -- 使用CAPI访问mysql服务器

1. 前期准备

在前期我们学习mysql的时候通常都会在服务器中使用相应的下载器下载mysql-server包,这个包中包含了运行数据库所需的程序(mysqld)和简单的命令行客户端(mysql),并没有包含写代码所使用的头文件和静态库,所以我们需要下载使用.

在这里使用的是ubuntu的apt来下载工具包 libmysqlclient-dev,可以使用命令

sudo apt-get install libmysqlclient-dev

我这里是已经安装好的了

安装好以后在usr/include/ 目录下应该会出现一个mysql的文件夹,其中就包含着需要用到的各种头文件

动静态库在/usr/lib/x86_64-linux-gnu/目录下

接下来写一小段代码来验证接口可用性

cpp 复制代码
#include <iostream>
#include <mysql/mysql.h>    // mysql api的头文件

using std::cout;
using std::endl;

int main()
{
    // 查询mysql当前服务器版本
    cout << "mysql client version: " << mysql_get_client_info() << endl;
    return 0;
}

编译的时候需要使用-l 指定链接的静态库名称,否则就会报错显示 undefined reference

g++ main.cpp -o main -lmysqlclient

此时可以看到我的mysql客户端的版本号是8.0.42,说明API是可用的

对于mysql的API可以在官方文档中进行查看,这里就不列举了

2. 连接mysql服务器并使用

2.1 初始化mysql库

在连接mysql服务器之前需要先初始化mysql服务器。官方文档中的初始化接口为mysql_init(),初始化成功会获得一个MYSQL* 的句柄,失败了返回NULL。当关闭连接的时候需要调用mysql_close().

初始化以及关闭链接代码:

cpp 复制代码
    // 初始化mysql
    MYSQL* myconnect = mysql_init(nullptr);
    if (nullptr == myconnect) {
        cout << "mysql 初始化失败" << endl;
        return 1;
    }

    mysql_close(myconnect);  // 关闭Mysql链接

2.2 连接mysql服务器

我们可以使用mysql_real_connect 来连接mysql服务器,以下是具体的参数以及说明:

参数:

mysql: MYSQL*指针 host: 连接的主机

user: 哪个用户连接的服务器 password: 当前连接服务器用户的密码

db: 需要访问的数据库 port: mysql服务器的端口号

unix_socket: UNIX 域套接字文件的路径,默认填nullptr即可

client_flag: 客户端连接标志位,默认填0即可

第一个参数 MYSQL是 C api中一个非常重要的变量(mysql_init的返回值),里面内存非常丰富,有port,dbname,charset 等连接基本参数。它也包含了一个叫 st_mysql_methods的结构体变量,该变量里面保存着很多函数指针,这些函数指针将会在数据库连接成功以后的各种数据操作中被调用.

返回值:

成功返回一个MYSQL*指针,失败了返回NULL.

注意连接的时候需要确保mysql的服务器在运行,否则会连接失败.

cpp 复制代码
// 配置信息
const std::string host = "127.0.0.1";
const std::string user = "toutie";
const std::string password = "";
const std::string db = "toutie_db";
unsigned int port = 3306;

if (mysql_real_connect(myconnect, host.c_str(), user.c_str(), password.c_str(), db.c_str(), port, nullptr, 0) == nullptr) {
        cout << "mysql connect fail!" << endl;
        return 2;
    }
    cout << "mysql connect successful!" << endl;
    while (1) {
        // 当前客户端正在访问  
        cout << "mysql client is running" << endl;
        sleep(5);
    }

测试连接:

可以看到确实有一个toutie用户连接到了服务器

2.3 模拟命令行操作数据库

我们可以使用mysql_query()接口对数据库进行增删查改等操作

官方的文档说明:

参数说明:

mysql: MYSQL*指针,其中包含许多基本参数,这里不做赘述

stmt_str: 控制指令,即需要对数据库进行的操作

返回值:

返回0代表语句执行成功, 非0代表语句执行失败

从外部获取命令并使用mysql_query()对数据库进行操作:

1)准备一张user表格

2)构建循环输入并操作数据库的代码

cpp 复制代码
std::string stmtStr = "";
    while (true) {
        std::cout << "mysql >>";
        // 如果输入的命令是quit或者输入流被终止就退出
        if (!std::getline(std::cin, stmtStr) || stmtStr == "quit") {
            cout << "bye bye" << endl;
            break;
        }

        int n = mysql_query(myconnect,stmtStr.c_str());
        if (n == 0) {
            cout << stmtStr << " success" << endl;
        }
        else {
            cout << stmtStr << " faild" << endl;
        }
    }

3)测试代码

此时该用户没有其他数据库的权限,即使在代码中访问其他数据库的时候也是不允许的.

这意味着,我们在代码之中对mysql服务器所做的操作与在服务器中直接操作是一样的.

ps: 注意数据库和当前cpp的编码方式,需要统一编码,否则在插入的时候就可能会出现乱码

mysql_set_character_set(myconnect, "utf8"); // 数据库表为utf8

2.4 对查询结果进行获取

在我们上面使用mysql_query()来执行sql语句的时候,确实能够完成所有的mysql操作,但是对于select操作来说,查是查了,但是没有返回给调用的这一层,看起来十分不合理,所以我们需要对select特殊处理.

1)执行查询语句

cpp 复制代码
    // 执行查询语句
    std::string sql = "select * from user";
    int n = mysql_query(myconnect, sql.c_str());

2)转储查询结果

当执行 mysql_query(conn, "SELECT ...") 时:

  • 它仅仅是向 MySQL 服务器发送了这条指令。

  • 它的返回值 int 只是告诉你:"指令发送成功了吗?" (0 表示成功,非 0 表示失败)。

  • 此时,数据(查询结果)还停留在服务器的发送缓冲区 ,或者刚刚到达客户端的网络缓冲区,并没有格式化存储在 MYSQL* 这个结构体里供你直接访问行数据。

需要获取查询到的结果必须通过 mysql_store_result(MYSQL *mysql) :

  • 它会把所有数据从服务器全部拉取到客户端的内存中。

  • 它返回一个 MYSQL_RES* 指针。这是真正存储查询结果(行、列数据)的地方.

MYSQL结构体中的部分属性

MYSQL_RES中的部分属性

所以:

MYSQL* 存的是连接的状态非查询操作的反馈

MYSQL_RES*才存着具体的查询数据

3)从MYSQL_RES指针中获取列数以及行数

这里可以使用以下两个api获取

cpp 复制代码
        // 获取结果集的行数
        int rows = mysql_num_rows(res);
        // 获取结果集的列数
        int cols = mysql_num_fields(res);

4)获取列名称

使用**mysql_fetch_fields(MYSQL_RES*)**可以获取各列的相关属性

返回值是一个结构体指针MYSQL_FIELD,这个指针包含列的各种属性:

以下标的形式按列遍历MYSQL_FIELD结构体,打印列名

cpp 复制代码
// MYSQL_FIELD结构体包含了列的所有属性
MYSQL_FIELD* fileds = mysql_fetch_fields(res);
for (int i = 0; i < cols; i++) {
       cout << fileds[i].name << "\t";
    }

5)获取各行数据

可以通过**mysql_fetch_row(MYSQL_RES*)**获取每一行的结果集.

cpp 复制代码
// 遍历结果集,获取查询结果
for (int i = 0; i < rows; i++) {
     // 获取一行的结果
     MYSQL_ROW row = mysql_fetch_row(res);
     for (int j = 0; j < cols; j++) {
          cout << row[j] << "\t";
     }
     cout << "\n";
}

这个函数能"自动迭代"去遍历每一行结果集,是因为这个结构体内部维护了一个"游标"( MYSQL_ROWS *data_cursor**)** 。

在源码中这个MYSQL_ROWS结构体中储存着下一个MYSQL_ROWS* next的指针,对应的MYSQL_ROW数据,还有一个length数据长度.

在源码中我们还可以看到 typedef char **MYSQL_ROW ,也就是说只要拿到了data_cursor就可以获取到MYSQL_ROWdata,然后像访问数组一样 去访问该行的每一列的数据(row[0], row[1]....).

自动遍历的流程: 函数内部执行首先会去获取当前data_cursor 中的data,然后执行 data_cursor = data_cursor->next 的关键动作,类似链表的迭代器遍历下一个节点的指针,当下一次再获取的时候就是新的data了。当data_cursor为空的时候就不会继续往下迭代了.

6)测试获取查询到的数据

3. 结语

掌握 MySQL C API 是后端开发的基石。虽然现代开发中我们常使用各种 ORM 框架,但理解底层的交互逻辑与内存模型,能让我们在面对复杂业务场景和性能优化时更加游刃有余。希望大家能亲手敲一遍代码,在实践中加深对这些概念的理解。

相关推荐
Zzzzmo_14 小时前
【MySQL】数据类型 及 表的操作
数据库·mysql
L16247614 小时前
linux系统中YUM安装MySQL数据库详细教程
linux·数据库·mysql
残风也想永存14 小时前
【MySQL】事务管理
数据库·mysql
wengad14 小时前
mongoDB安全漏洞CVE-2025-14847修复方案
数据库·mongodb
阿豪学编程14 小时前
【Linux】线程基础:控制逻辑与封装指南
linux·运维·服务器
kuankeTech14 小时前
海南封关供应链重构:外贸ERP如何成为企业的“数字海关”
大数据·数据库·人工智能·重构·软件开发·erp
ChrylZzz14 小时前
oracle读写分离搭建需要几台服务器
数据库
ZeroNews内网穿透14 小时前
轻量级自托管Git服务:Gitea私有化部署与公网访问
服务器·网络·数据库·git·gitea
bst@微胖子14 小时前
CrewAI+FastAPI的Pipelines功能实现多CrewAI工作流以及Flows功能实现复杂工作流
服务器·windows·fastapi