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 框架,但理解底层的交互逻辑与内存模型,能让我们在面对复杂业务场景和性能优化时更加游刃有余。希望大家能亲手敲一遍代码,在实践中加深对这些概念的理解。