一、C语言访问MySQL
除了我们前面讲的通过命令行的方式访问数据库,我们还可以通过使用MySQL原生的C语言的API来连接和访问数据库。要使用C语言连接 mysql,我们需要使用mysql官网提供的库。
我们使用C接口库来进行连接。要正常地使用C接口来访问数据库,我们需要做一些准备工作。首先需要保证MySQL服务有效,然后要在官网上下载适合自己平台的mysql connect库。
在MySQL官网中找到C API,点击进去就可以进行安装。

我们可以通过mysql_get_client_info()函数,来验证我们的引入是否成功:
cpp
#include <stdio.h>
#include <mysql.h>
int main()
{
printf("mysql client version: %s\n", mysql_get_client_info());
return 0;
}
在输入编译命令时我们需要带上`mysql_config --cflags --libs`,编译指令如下:
bash
gcc test.c -o test `mysql_config --cflags --libs`
其中:
mysql_config是MySQL 开发包自带脚本,用来自动生成编译所需参数,避免硬编码路径,方便移植。
--cflags输出-Ixxx,用于指定 mysql.h 头文件目录,解决找不到头文件的编译报错。
--libs输出-Lxxx -lmysqlclient,用于指定库文件位置并链接 libmysqlclient.so,解决函数未定义的链接报错。
反引号 `可以让shell先执行里面命令,把输出替换进gcc命令。
引入成功的打印结果如下:

二、MySQL基础数据结构

- MYSQL
这个结构体代表一条数据库连接的句柄(handler),几乎所有 MySQL C API 函数都要用到它。 需要注意的是,不要自己拷贝复制MYSQL结构体,这样拷贝出来的副本不保证能正常使用。
对应接口:mysql_init、mysql_real_connect、mysql_close。这三个接口都操作这个结构体。
- MYSQL_RES
代表会返回行数据的查询结果,只有SELECT / SHOW / DESCRIBE / EXPLAIN这类查询才会产生它,这一块数据整体叫做结果集 (result set)。
当我们使用mysql_store_result接口就会返回它,使用完后必须使用mysql_free_result释放。 insert/update/delete操作不会产生 MYSQL_RES。
- MYSQL_ROW
代表一行数据,底层实现是一个字节串数组。注意,不能直接当成 C 语言以\0结尾的字符串。如果字段存二进制数据(比如图片、blob),内容内部本身就会包含\0,直接用strcpy/printf会截断。
使用mysql_fetch_row()接口时就会返回MYSQL_ROW。
- MYSQL_FIELD
存放字段元数据(metadata):字段名、字段类型、长度、表名等描述信息。 需要注意的是,MYSQL_FIELD就是描述一列字段的元数据,而不是表里面的行数据,真实数据都在 MYSQL_ROW中。
使用接口mysql_fetch_fields/mysql_fetch_field会使用到这个字段
- MYSQL_FIELD_OFFSET
用来表示字段列表里的偏移下标,配合接口mysql_field_seek()来使用。下标从 0 开始,代表一行里第几个列。 用于在结果集里移动字段读取的位置。
- my_ulonglong
64位无符号整数类型。
使用接口mysql_affected_rows时会返回这个类型。
- my_bool
布尔类型:非0代表 true,0代表 false。
对应接口:mysql_autocommit、mysql_commit、mysql_rollback的返回值类型。
三、MySQL接口介绍
在官网中可以查到C API的使用文档,我们可以在里面查询MySQL常用函数的使用方法。


要使用MySQ库,就必须先进行初始化,初始化的接口为:
cpp
MYSQL *mysql_init(MYSQL *mysql);
使用示例:MYSQL *mfp = mysql_init(NULL);
初始化完毕之后,我们需要先连接数据库,再进行后续操作(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);
第一个参数MYSQL是 C api 中一个非常重要的变量(mysql_init 的返回值),里面有port,dbname,charset等连接基本参数。它也包含了一个叫st_mysql_methods的结构体变量,该变量里面保存着很多函数指针,这些函数指针将会在数据库连接成功以后的各种数据操作中被调用。
建立完连接之后,获取英文没有问题,如果获取中文是乱码,就需要使用接口mysql_set_character_set设置链接的默认字符集是utf8(原始默认是latin1)
cpp
int mysql_set_character_set(MYSQL *mysql, const char *csname);
下发命令使用接口mysql_query:
cpp
int mysql_query(MYSQL *mysql, const char *q);
第一个参数是上面介绍过的MySQL,第二个参数为要执行的SQL语句。
当sql语句执行完以后,如果是查询语句,我们就需要读取数据;如果是update,insert等语句,那么我们只需要看下操作成功与否即可。获取执行结果我们使用接口mysql_store_result:
cpp
MYSQL_RES *mysql_store_result(MYSQL *mysql);
在获取完结果集后,我们可以通过mysql_num_rows接口来获取结果集中有多少行数据:
cpp
mysql_num_rows(MYSQL_RES *res)
需要注意的是,只有当接口mysql_store_result一次性把结果读到本地内存之后,这个函数才是准确的;如果是 mysql_use_result 流式读取,在读完所有行之前,它返回的数字不准。
除了获取行数之外,同时还有供我们获取列数的接口mysql_num_fields:
cpp
mysql_num_fields(MYSQL_RES *res)
MySQL会将所有读取出来的数据都当作字符串处理,所以res在保存数据时会先将这些数据规制化。在res内部相当于有多个二级指针char**,指向具体的某一行,第二层指针指向行中的某一列的数据。MySQL为了更好的支持遍历,提供了下面这个函数:
cpp
MYSQL_ROW mysql_fetch_row(MYSQL_RES *res);
它会返回一个MYSQL_ROW变量,MYSQL其实就是char **,可以当作一个二维数组来时用,使用示例如下:
cpp
i = 0;
MYSQL_ROW line;
for(; i < nums; i++){
line = mysql_fetch_row(res);
int j = 0;
for(; j < fields; j++){
cout << line[j] << " ";
}
cout << endl;
}
我们可以使用mysql_fetch_fields接口来获取结果集中的字段名、字段类型、长度、表名等属性:
cpp
mysql_fetch_fields(MYSQL_RES *res)
由于mysql_store_result接口会在堆上开辟内存,存放查询返回的结果集数据。所以当我们程序退出时就需要把开辟的这部分内存给释放掉。而mysql_free_result接口就是用来释放MYSQL_RES* 结果集占用的内存的:
cpp
void mysql_free_result(MYSQL_RES *result);
使用完毕之后,需要将mysql的连接关闭,需要使用到接口:
cpp
void mysql_close(MYSQL *mysql);
完整使用示例:
cpp
#include <iostream>
#include <string>
#include <mysql/mysql.h>
// MySQL 连接信息
const std::string host = "localhost";
const std::string user = "connector";
const std::string passwd = "123456";
const std::string db = "conn";
// 重点:MySQL服务默认端口 3306,不是8080!8080是http端口
const unsigned int port = 3306;
int main()
{
// 1. 初始化MYSQL句柄
MYSQL *my = mysql_init(nullptr);
if (my == nullptr)
{
std::cerr << "mysql_init 失败: " << mysql_error(my) << std::endl;
return 1;
}
// 2. 建立与MySQL服务端连接
if (mysql_real_connect(my, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
{
std::cerr << "mysql_real_connect 连接失败: " << mysql_error(my) << std::endl;
mysql_close(my);
return 2;
}
std::cout << "✅ MySQL 连接成功" << std::endl;
// 3. 设置连接字符集,解决中文乱码(utf8mb4 支持emoji、生僻字,推荐)
if (mysql_set_character_set(my, "utf8mb4") != 0)
{
std::cerr << "设置字符集失败: " << mysql_error(my) << std::endl;
mysql_close(my);
return 3;
}
std::cout << "✅ 连接字符集设置为 utf8mb4" << std::endl;
//DML语句(update / insert / delete,无结果集)
std::string sql_update = "update user set name='Jimmy' where id=2";
int ret = mysql_query(my, sql_update.c_str());
if (ret == 0)
{
std::cout << "SQL执行成功:" << sql_update << std::endl;
// 获取本次SQL影响的行数
my_ulonglong affect_rows = mysql_affected_rows(my);
std::cout << "本次影响行数:" << affect_rows << std::endl;
}
else
{
std::cerr << "SQL执行失败:" << sql_update << std::endl;
std::cerr << "错误信息:" << mysql_error(my) << std::endl;
}
//DQL查询语句(select,有结果集,配套前面学的fetch_row)
std::string sql_select = "select id, name from user";
ret = mysql_query(my, sql_select.c_str());
if (ret == 0)
{
std::cout << "\n===== 查询结果 =====" << std::endl;
// 获取结果集
MYSQL_RES* res = mysql_store_result(my);
if (res == nullptr)
{
std::cerr << "mysql_store_result 获取结果集失败:" << mysql_error(my) << std::endl;
}
else
{
// 获取列数量
unsigned int cols = mysql_num_fields(res);
MYSQL_ROW row;
// 循环读取每一行(标准安全写法,推荐)
while ((row = mysql_fetch_row(res)) != nullptr)
{
for (unsigned int j = 0; j < cols; j++)
{
// row[j] 为NULL代表数据库该字段是NULL
if (row[j] == nullptr)
std::cout << "(NULL) ";
else
std::cout << row[j] << " ";
}
std::cout << std::endl;
}
//释放结果集!!
mysql_free_result(res);
}
}
else
{
std::cerr << "查询SQL失败:" << sql_select << std::endl;
std::cerr << "错误信息:" << mysql_error(my) << std::endl;
}
// 4. 关闭连接,释放句柄
mysql_close(my);
std::cout << "\n连接已关闭" << std::endl;
return 0;
}