MySQL C API 核心知识点精讲
同学们,今天我们来学习MySQL C API ,简单说,它就是 MySQL 官方提供的C 语言操作 MySQL 数据库的函数库------ 之前我们都是用 MySQL 命令行执行 SQL,而实际开发中(比如写 C/C++ 项目),需要通过代码直接操作数据库,这时候就要用到 MySQL C API 了。
整个知识点我们按 **「环境准备→核心 API 分模块讲解→固定使用流程→示例程序拆解」** 来讲,API 部分会讲清作用、参数、返回值、注意事项 ,同时标注高频考点、易错点,保证大家能理解、会用、能调试。
所有内容基于 Linux 环境(Ubuntu/CentOS),Windows 环境仅库文件配置不同,API 用法完全一致。
一、前置准备:环境搭建与编译规则
使用 MySQL C API 的第一步是搞定环境,这是基础,代码写得再对,环境错了也跑不起来,核心就 3 点:装库、引头文件、编译加链接选项。
1. 安装依赖库
Linux 下安装libmysqlclient-dev(MySQL 客户端开发库,包含头文件和链接库):
cpp
sudo apt install libmysqlclient-dev # Ubuntu/Debian
# CentOS/RHEL用这个:sudo yum install mysql-devel
2. 代码中引入头文件
所有使用 MySQL C API 的 C 文件,必须在开头包含头文件,它定义了 API 函数、MYSQL 相关结构体:
cpp
#include <mysql/mysql.h>
3. 编译时的链接选项
gcc 编译时,必须加-lmysqlclient,告诉编译器链接 MySQL 客户端库,否则会报「未定义的引用」错误。编译命令模板:
cpp
gcc 你的代码文件.c -o 可执行程序名 -lmysqlclient
比如测试版本的代码编译:
cpp
gcc t_mysql_version.c -o t_mysql_version -lmysqlclient
小测试:验证环境是否正常
写一段简单代码调用mysql_get_client_info()(获取 MySQL 客户端版本),编译执行能输出版本号,说明环境没问题:
cpp
#include <stdio.h>
#include <mysql/mysql.h>
int main() {
printf("MySQL Client Version: %s\n", mysql_get_client_info());
return 0;
}
二、核心 API 分模块讲解
MySQL C API 的函数是按操作流程设计 的,我们按「库初始化→对象初始化→建立连接→执行 SQL→错误诊断→处理结果→释放资源 / 关闭连接」的顺序讲,每个函数讲清干什么、怎么用、注意什么。
先明确一个核心概念:MYSQL 结构体 MYSQL 是 MySQL C API 的核心句柄(handle),相当于操作 MySQL 数据库的「总把手」,后续所有操作(连接、执行 SQL、获取结果)都要基于这个结构体的对象,必须先初始化才能用。
模块 1:MySQL 库的全局初始化与终止
作用:对 MySQL 客户端库做全局的初始化 / 释放 ,是所有 MySQL C API 调用的前置 / 后置操作,整个程序只需要执行一次。
1. mysql_library_init():初始化 MySQL 库
cpp
int mysql_library_init(int argc, char** argv, char** groups);
-
返回值:成功返回 0,失败返回非 0;
-
参数 :新版本中argc=0、argv=NULL、groups=NULL 即可(旧版本用于嵌入式服务器,现已废弃);
-
核心注意点 :线程不安全 !必须在程序创建任何子线程之前调用,否则会出现未知错误;
-
用法 :
cppif (mysql_library_init(0, NULL, NULL)) { fprintf(stderr, "MySQL库初始化失败!\n"); exit(1); // 失败直接退出 }
2. mysql_library_end():终止 MySQL 库
cpp
void mysql_library_end(void);
- 作用:释放 MySQL 库占用的全局资源,避免内存泄漏;
- 调用时机 :程序结束前,所有 MySQL 操作完成、连接关闭后调用;
- 无参数、无返回值,直接调用即可。
模块 2:MYSQL 对象的初始化(操作数据库的句柄)
作用:初始化一个MYSQL 类型的对象,后续建立连接、执行 SQL 都要用到这个对象,相当于为「数据库连接」创建一个载体。
cpp
MYSQL* mysql_init(MYSQL* mysql);
-
参数 :传入一个 MYSQL 指针,分两种情况:
- 传
NULL:函数会在堆上自动申请内存创建并初始化 MYSQL 对象,返回对象地址; - 传已定义的 MYSQL 对象地址:函数直接初始化该对象,返回原地址;
- 传
-
返回值:成功返回初始化后的 MYSQL * 指针,失败返回 NULL(内存不足导致);
-
常用用法 (推荐传 NULL,无需手动定义对象):
cppMYSQL* mysql = mysql_init(NULL); if (mysql == NULL) { fprintf(stderr, "MYSQL对象初始化失败!\n"); exit(1); }
模块 3:建立与 MySQL 服务器的连接
作用:通过初始化后的 MYSQL 对象,连接到指定的 MySQL 服务器 ,指定要操作的数据库、用户名、密码等。核心函数:mysql_real_connect()
cpp
MYSQL* mysql_real_connect(
MYSQL* mysql, // 已初始化的MYSQL对象指针
const char* host, // 主机名/IP:NULL/localhost=连接本地MySQL
const char* user, // MySQL用户名(如root)
const char* passwd, // MySQL密码
const char* db, // 要连接的数据库名(如test)
unsigned int port, // 端口:0=使用默认3306
const char* unix_socket, // 本地套接字:填NULL即可
unsigned long client_flag // 连接标志:填0即可
);
-
返回值 :成功返回第一个参数
mysql(原对象指针),失败返回 NULL; -
核心注意点 :连接失败时,必须用错误诊断函数查原因(后面讲),不要只打印 "连接失败";
-
常用用法 (连接本地 MySQL,数据库 test,用户名 root,密码 1234):
cppif (mysql_real_connect(mysql, NULL, "root", "1234", "test", 0, NULL, 0) == NULL) { // 连接失败,打印错误信息 fprintf(stderr, "连接失败:%s\n", mysql_error(mysql)); exit(1); }
模块 4:执行 SQL 语句
作用:通过已建立连接的 MYSQL 对象,执行任意 SQL 语句(增删改查、建表、删库等),提供了两个函数,根据场景选择即可。
1. mysql_query():执行普通 SQL 语句(最常用)
cpp
int mysql_query(MYSQL* mysql, const char* stmt);
- 参数 :
mysql:已连接的 MYSQL 对象指针;stmt:要执行的 SQL 语句字符串,注意 3 点 :① 以\0结尾(C 语言普通字符串即可);② 不能以;或 \g 结尾 (和命令行不同);③ 只能执行无二进制数据的 SQL(如普通增删改查);
- 返回值:成功返回 0,失败返回非 0;
2. mysql_real_query():执行含二进制数据的 SQL
cpp
int mysql_real_query(MYSQL* mysql, const char* stmt, unsigned long length);
- 适用场景 :SQL 语句中包含二进制数据 (如图片、文件内容,可能含
\0字符),mysql_query()会把\0当作字符串结尾,导致 SQL 截断,此时用这个函数; - 参数 :多了
length,表示 SQL 语句的字节长度 (手动指定,避免\0截断); - 额外优势 :比
mysql_query()快,因为不需要调用strlen()计算字符串长度; - 返回值:成功返回 0,失败返回非 0;
通用用法(执行 SELECT 查询)
cpp
char sql[] = "SELECT * FROM employees"; // 无;,普通SQL
if (mysql_query(mysql, sql)) { // 失败返回非0,直接判断
fprintf(stderr, "执行SQL失败:%s\n", mysql_error(mysql));
exit(1);
}
模块 5:错误诊断(调试必备!)
作用:所有 MySQL C API 函数执行失败后,都要用这两个函数查具体原因 ,避免只知道 "失败",不知道为什么失败,是调试的核心函数。这两个函数的规则:返回最近一次 MySQL API 操作的错误信息,操作成功则返回 "无错误"。
mysql_errno():用来判断有没有出错。mysql_error():用来描述错在哪里。
1. mysql_errno():获取错误码
cpp
unsigned int mysql_errno(MYSQL* mysql);
- 返回值:0 = 无错误,非 0 = 错误码(可查 MySQL 官方文档对应错误原因);
2. mysql_error():获取错误描述字符串
cpp
const char* mysql_error(MYSQL* mysql);
- 返回值 :空字符串
""= 无错误,非空 = 具体的错误原因(如 "访问被拒绝""表不存在");
核心用法
所有 API 执行后,失败则调用这两个函数,示例:
运行
cpp
if (mysql_real_connect(...) == NULL) {
// 同时打印错误码和错误描述,调试更方便
fprintf(stderr, "错误码:%d,错误原因:%s\n", mysql_errno(mysql), mysql_error(mysql));
exit(1);
}
模块 6:处理 SQL 执行结果
执行 SQL 后,分两种场景 ,处理方式完全不同,这是 MySQL C API 的重点和难点,一定要区分清楚:
场景 1:执行查询类 SQL (SELECT/SHOW/DESC)→ 有结果集需要提取和遍历
场景 2:执行修改类 SQL (INSERT/UPDATE/DELETE/CREATE)→ 无结果集,只需获取受影响的行数
子模块 1:处理查询类 SQL 的结果集(核心)
步骤:提取结果集→获取行 / 列数→遍历结果→释放结果集,对应一组函数,缺一不可。
1. mysql_store_result():提取并保存结果集
把 MySQL 服务器返回的查询结果,一次性读取到客户端内存中 ,返回结果集句柄MYSQL_RES*:
cpp
MYSQL_RES* mysql_store_result(MYSQL* mysql);
-
返回值 :① 成功有结果→返回
MYSQL_RES*(结果集指针);② 成功无结果(如查询无数据)→返回 NULL;③ 失败(如 SQL 错误、网络问题)→返回 NULL; -
核心难点 :返回 NULL 时,如何区分「无结果」和「执行失败」? 解决方案:调用
mysql_errno(),非 0 = 失败,0 = 无结果 :cppMYSQL_RES* result = mysql_store_result(mysql); if (result == NULL) { if (mysql_errno(mysql) != 0) { // 失败,打印错误 fprintf(stderr, "提取结果集失败:%s\n", mysql_error(mysql)); exit(1); } else { // 无结果,正常提示 printf("查询结果为空!\n"); return 0; } }
2. mysql_num_rows():获取结果集的记录数(行数)
cpp
uint64_t mysql_num_rows(MYSQL_RES* result);
- 参数 :
mysql_store_result()返回的结果集指针; - 返回值:结果集的行数,永远成功(无结果则返回 0);
3. mysql_num_fields():获取结果集的字段数(列数)
cpp
unsigned int mysql_num_fields(MYSQL_RES* result);
- 参数:结果集指针;
- 返回值:结果集的列数,永远成功;
4. mysql_fetch_row():遍历结果集,获取下一条记录
cs
MYSQL_ROW mysql_fetch_row(MYSQL_RES* result);
- 核心概念 :
MYSQL_ROW是字符指针数组 (char**),数组的每个元素对应记录的一列值,列值以字符串形式存储(MySQL 会自动把数值转字符串); - 返回值 :成功返回当前记录的
MYSQL_ROW,无更多记录则返回 NULL; - 用法 :用
while循环遍历,直到返回 NULL;
5. mysql_free_result():释放结果集(必做!)
cpp
void mysql_free_result(MYSQL_RES* result);
- 作用 :释放
mysql_store_result()申请的内存,避免内存泄漏; - 调用时机:结果集遍历完成后,立即调用;
- 无返回值,直接传结果集指针即可。
结果集处理完整示例
cpp
// 提取结果集
MYSQL_RES* result = mysql_store_result(mysql);
if (result == NULL) { /* 判错逻辑 */ }
// 获取行/列数
uint64_t row_num = mysql_num_rows(result);
unsigned col_num = mysql_num_fields(result);
printf("共%d行,%d列\n", row_num, col_num);
// 遍历结果集
MYSQL_ROW row;
while ((row = mysql_fetch_row(result))) { // 取一条记录,直到NULL
for (int i = 0; i < col_num; i++) { // 遍历每一列
printf("%s\t", row[i]); // 列值是字符串,直接打印
}
printf("\n");
}
// 释放结果集
mysql_free_result(result);
子模块 2:处理修改类 SQL 的结果(无结果集)
执行 INSERT/UPDATE/DELETE 后,不需要结果集,只需知道有多少行数据被修改 ,用mysql_affected_rows():
cpp
uint64_t mysql_affected_rows(MYSQL* mysql);
-
参数:已连接的 MYSQL 对象指针;
-
返回值 :① 成功→返回受影响的行数(0 = 无行被修改,如 UPDATE 的条件不匹配);② 失败→返回
(uint64_t)-1; -
用法 (执行 INSERT 后获取插入行数):
cppchar sql[] = "INSERT INTO employees(department) VALUES('研发部')"; if (mysql_query(mysql, sql)) { /* 判错 */ } uint64_t n = mysql_affected_rows(mysql); printf("成功插入%d行数据\n", n); // 输出:成功插入1行数据
模块 7:关闭数据库连接
作用:断开与 MySQL 服务器的连接,释放 MYSQL 对象的内存 (如果是mysql_init(NULL)创建的对象)
cpp
void mysql_close(MYSQL* mysql);
- 参数:已建立连接的 MYSQL 对象指针;
- 核心注意点 :
- 调用后,该 MYSQL 对象不能再被使用;
- 如果
mysql_init()传的是 NULL(堆上创建的对象),mysql_close()会自动释放对象的内存,无需手动 free; - 调用时机:所有数据库操作完成、结果集释放后。
三、MySQL C API 固定使用流程
所有用 MySQL C API 操作数据库的 C 程序,流程都是固定的,记熟这个流程,写代码就不会乱,按步骤来即可:
plaintext
cpp
1. 初始化MySQL库:mysql_library_init(0, NULL, NULL)
2. 初始化MYSQL对象:mysql_init(NULL)
3. 建立数据库连接:mysql_real_connect(...)
4. 执行SQL语句:mysql_query()/mysql_real_query()
5. 处理执行结果:
- 查:提取结果集→遍历→释放结果集
- 增删改:获取受影响行数
6. 释放资源:
- 有结果集:mysql_free_result()
7. 关闭数据库连接:mysql_close()
8. 终止MySQL库:mysql_library_end()
核心原则 :先初始化,再使用;先释放,再关闭,避免内存泄漏和资源占用。
四、完整示例程序拆解(查询 employees 表)
结合我们之前创建的employees员工表,把文档中的示例程序逐核心步骤拆解,对应上面的流程,让大家把代码和知识点对应起来,理解每一步的作用:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <mysql/mysql.h>
int main() {
// 步骤1:初始化MySQL库
if (mysql_library_init(0, NULL, NULL)) {
fprintf(stderr, "MySQL库初始化失败\n");
exit(1);
}
// 步骤2:初始化MYSQL对象
MYSQL* mysql = mysql_init(NULL);
if (mysql == NULL) {
fprintf(stderr, "MYSQL对象初始化失败\n");
exit(1);
}
// 步骤3:建立连接(本地root,密码1234,数据库test)
if (mysql_real_connect(mysql, NULL, "root", "1234", "test", 0, NULL, 0) == NULL) {
fprintf(stderr, "连接失败:%s\n", mysql_error(mysql));
exit(1);
}
// 步骤4:执行SQL(查询employees表所有记录)
char sql[] = "SELECT * FROM employees";
if (mysql_query(mysql, sql)) {
fprintf(stderr, "执行SQL失败:%s\n", mysql_error(mysql));
exit(1);
}
// 步骤5:处理查询结果------提取结果集
MYSQL_RES* result = mysql_store_result(mysql);
if (result == NULL) {
if (mysql_errno(mysql)) { // 区分失败和无结果
fprintf(stderr, "提取结果集失败:%s\n", mysql_error(mysql));
exit(1);
} else {
printf("查询结果为空\n");
mysql_close(mysql);
mysql_library_end();
return 0;
}
}
// 处理查询结果------获取行/列数并打印
uint64_t rows = mysql_num_rows(result);
unsigned fields = mysql_num_fields(result);
printf("总记录数:%lu\n", rows);
// 处理查询结果------遍历结果集
MYSQL_ROW row;
while ((row = mysql_fetch_row(result))) {
for (unsigned i = 0; i < fields; i++) {
printf("%s\t", row[i]);
}
printf("\n");
}
// 步骤6:释放结果集
mysql_free_result(result);
// 步骤7:关闭连接
mysql_close(mysql);
// 步骤8:终止MySQL库
mysql_library_end();
return 0;
}
编译执行:
cpp
gcc test_mysql.c -o test_mysql -lmysqlclient
./test_mysql
就能在终端打印出employees表的所有记录。
五、高频易错点总结(避坑必看)
mysql_library_init()必须在创建线程前调用,线程不安全;- SQL 语句不能以;或 \g 结尾,和 MySQL 命令行的区别;
mysql_store_result()返回 NULL 时,一定要判错,区分「无结果」和「失败」;- 结果集使用后必须调用
mysql_free_result(),否则内存泄漏; - 连接失败 / 执行 SQL 失败时,必须用
mysql_errno()/mysql_error()查原因,不要只打印简单提示; - 二进制数据的 SQL 必须用
mysql_real_query(),不能用mysql_query(); mysql_close()会自动释放mysql_init(NULL)创建的 MYSQL 对象,无需手动 free;- 整个程序中,
mysql_library_init()和mysql_library_end()只需要执行一次。