一、联动前提:准备工作与库文件解析
要实现 C 语言与 MySQL 的连接,需先完成环境配置和库文件准备,这是后续开发的基础。
1. 三大核心准备步骤
确保 MySQL 服务正常运行:C 语言程序通过网络(TCP/IP)与 MySQL 交互,需先确认数据库服务已启动(如 Linux 下执行 systemctl start mysqld),否则会因连接超时导致失败。
下载 MySQL C 接口库 :需从 MySQL 官网下载适配当前操作系统(Windows/Linux/macOS)的 Connector/C 库,该库包含 C 语言操作 MySQL 所需的头文件和函数实现。
理解库文件结构:下载后的库文件分为 include 和 lib 两个核心目录,分别对应方法声明和实现,结构如下(以 Linux 环境为例):
sql
.
├── include/ # 头文件目录,包含所有接口方法声明
│ ├── mysql.h # 核心头文件,包含多数函数和结构体定义
│ ├── errmsg.h # 错误信息定义
│ ├── mysql_com.h # 通信协议相关定义
│ └── ...(其他辅助头文件)
└── lib/ # 库文件目录,包含编译后的函数实现
├── libmysqlclient.a # 静态库文件
├── libmysqlclient.so # 动态库文件(Linux)
└── libmysqlclient.dylib # 动态库文件(macOS)
其中,include 目录的头文件用于代码编译时的语法校验,lib 目录的库文件用于链接阶段,将函数实现注入可执行程序。
二、第一步:验证库文件引入(编译与运行)
库文件下载后,需先通过简单代码验证是否能成功引入,避免后续开发因环境问题浪费时间。
1. 验证代码:获取 MySQL 客户端版本
通过调用 mysql_get_client_info() 函数(该函数返回当前使用的 MySQL 客户端版本),验证库文件是否正确关联:
cpp
#include <stdio.h>
#include <mysql.h> // 引入 MySQL C 接口核心头文件
int main() {
// 输出 MySQL 客户端版本
printf("MySQL Client Version: %s\n", mysql_get_client_info());
return 0;
}
2. 编译命令与参数解析
在 Linux 环境下,需通过 gcc 编译代码,同时指定头文件路径(-I)和库文件路径(-L),命令如下:
bash
gcc -o test_mysql test_mysql.c -I./include -L./lib -lmysqlclient
-I./include:#告诉编译器从当前目录的 include 文件夹查找头文件(如 mysql.h);
-L./lib:#指定库文件的搜索路径为当前目录的 lib 文件夹;
-lmysqlclient:#链接名为 mysqlclient 的库文件(编译器会自动匹配 libmysqlclient.a 或 libmysqlclient.so)。
3. 解决动态库加载问题
编译成功后,直接运行可执行文件(./test_mysql)可能出现以下错误:
sql
./test_mysql: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object file: No such file or directory
原因是系统默认的动态库搜索路径中没有 libmysqlclient.so,需通过环境变量 LD_LIBRARY_PATH 临时指定:
sql
export LD_LIBRARY_PATH=./lib # 将当前目录的 lib 文件夹加入动态库搜索路径
./test_mysql # 再次运行,成功输出版本信息
成功执行后,会输出类似以下结果,说明库文件引入正常:
sql
MySQL Client Version: 6.1.6
至此,C 语言与 MySQL 的环境联动已完成,可进入核心接口开发阶段。
三、核心流程:C 语言操作 MySQL 的 6 步曲
通过 C 语言操作 MySQL 需遵循固定流程:初始化 → 连接数据库 → 执行 SQL → 处理结果 → 释放资源 → 关闭连接。以下按步骤详解每个环节的接口使用。
1. 步骤 1:初始化 MySQL 环境(mysql_init)
在执行任何数据库操作前,必须先初始化 MySQL 句柄 (MYSQL 结构体指针),该句柄将贯穿整个数据库交互过程,存储连接参数、状态等信息。
函数原型:
cpp
MYSQL *mysql_init(MYSQL *mysql);
参数说明:若传入 NULL,函数会自动分配内存创建新的 MYSQL 句柄;若传入已存在的句柄,会对其重置。
示例代码:
cpp
MYSQL *mysql_conn = mysql_init(NULL); // 初始化句柄
if (mysql_conn == NULL) {
fprintf(stderr, "mysql_init failed: %s\n", mysql_error(mysql_conn));
return 1; // 初始化失败,退出程序
}
初始化失败通常是因内存不足,需通过 mysql_error() 函数获取具体错误信息。
2. 步骤 2:连接 MySQL 数据库(mysql_real_connect)
初始化句柄后,通过 mysql_real_connect 函数建立与数据库的 TCP 连接 ,需传入数据库地址、用户名、密码等核心参数。
函数原型:
cpp
MYSQL *mysql_real_connect(
MYSQL *mysql, // 已初始化的 MYSQL 句柄
const char *host, // 数据库主机地址(localhost/IP)
const char *user, // 数据库用户名
const char *passwd, // 数据库密码
const char *db, // 要连接的数据库名
unsigned int port, // 数据库端口(默认 3306)
const char *unix_socket, // Unix 域套接字(NULL 表示使用 TCP/IP)
unsigned long clientflag // 客户端标志(0 表示默认)
);
关键注意:连接成功后,若查询中文数据出现乱码,需通过 mysql_set_character_set(mysql_conn, "utf8"); 设置字符集(默认字符集为 latin1)。
示例代码:
cpp
// 连接数据库
if (mysql_real_connect(
mysql_conn,
"localhost", // 本地数据库
"root", // 用户名
"123456", // 密码
"test_db", // 要连接的数据库
3306, // 端口
NULL, // 不使用 Unix 套接字
0 // 默认客户端标志
) == NULL) {
fprintf(stderr, "Connect failed: %s\n", mysql_error(mysql_conn));
mysql_close(mysql_conn); // 关闭句柄,释放资源
return 1;
}
// 设置字符集为 UTF-8,解决中文乱码
mysql_set_character_set(mysql_conn, "utf8");
3. 步骤 3:执行 SQL 语句(mysql_query)
连接成功后,通过 mysql_query 函数执行 SQL 语句 (支持查询、插入、更新、删除等所有操作)。
函数原型:
cpp
int mysql_query(MYSQL *mysql, const char *q);
- 返回值:成功返回 0,失败返回非 0(可通过 mysql_error() 获取错误原因)。
- 参数说明:q 为要执行的 SQL 字符串(无需在末尾添加分号)。
示例代码:
cpp
const char *sql = "SELECT id, name, age FROM student"; // 查询学生表
if (mysql_query(mysql_conn, sql) != 0) {
fprintf(stderr, "Query failed: %s\n", mysql_error(mysql_conn));
mysql_close(mysql_conn);
return 1;
}
若执行插入 / 更新 / 删除等写操作(如 INSERT INTO student(name, age) VALUES('Alice', 20)),只需将 sql 字符串替换为对应语句,通过返回值判断执行结果即可。
4. 步骤 4:获取查询结果(mysql_store_result)
对于 SELECT 等查询类 SQL,执行 mysql_query 后需通过 mysql_store_result 函数获取结果集 ,该函数会将查询结果从数据库服务器读取到本地内存。
函数原型:
cpp
MYSQL_RES *mysql_store_result(MYSQL *mysql);
- 返回值:成功返回 MYSQL_RES 类型的结果集指针,失败返回 NULL。
- 注意事项:结果集占用的内存需手动释放(通过 mysql_free_result()),否则会导致内存泄漏。 示例代码:
cpp
MYSQL_RES *result = mysql_store_result(mysql_conn);
if (result == NULL) {
fprintf(stderr, "Get result failed: %s\n", mysql_error(mysql_conn));
mysql_close(mysql_conn);
return 1;
}
5. 步骤 5:解析结果集(4 个核心函数)
获取 MYSQL_RES 结果集后,需通过以下函数解析数据,包括行数、列数、列名和具体字段值。
函数名称 | 原型 | 功能说明 |
---|---|---|
mysql_num_rows | my_ulonglong mysql_num_rows(MYSQL_RES *res) | 获取结果集的总行数 |
mysql_num_fields | unsigned int mysql_num_fields(MYSQL_RES *res) | 获取结果集的总列数 |
mysql_fetch_fields | MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res) | 获取所有列的信息(如列名、类型) |
mysql_fetch_row MYSQL_ROW mysql_fetch_row(MYSQL_RES *res) | 逐行读取结果集,返回当前行的字段值(二维数组) |
解析示例代码:
cpp
// 1. 获取行数和列数
my_ulonglong row_count = mysql_num_rows(result); // 总行数
unsigned int field_count = mysql_num_fields(result); // 总列数
printf("Total Rows: %llu, Total Columns: %u\n", row_count, field_count);
// 2. 获取列名并输出
MYSQL_FIELD *fields = mysql_fetch_fields(result);
printf("Columns: ");
for (unsigned int i = 0; i < field_count; i++) {
printf("%s ", fields[i].name); // 输出列名(如 id name age)
}
printf("\n");
// 3. 逐行读取数据并输出
MYSQL_ROW row;
while ((row = mysql_fetch_row(result)) != NULL) {
for (unsigned int i = 0; i < field_count; i++) {
printf("%s ", row[i] ? row[i] : "NULL"); // 输出字段值(处理 NULL)
}
printf("\n");
}
sql
Total Rows: 2, Total Columns: 3
Columns: id name age
1 Alice 20
2 Bob 22
其中,mysql_fetch_row 每次调用会移动到下一行,返回 NULL 表示已读取完所有数据。
6. 步骤 6:释放资源与关闭连接
操作完成后,需按顺序释放结果集、关闭数据库连接,避免资源泄漏。
- 释放结果集:mysql_free_result(result);(必须在关闭连接前执行);
- 关闭连接:mysql_close(mysql_conn);(释放 MYSQL 句柄占用的内存)。
示例代码:
cpp
mysql_free_result(result); // 释放结果集
mysql_close(mysql_conn); // 关闭数据库连接
四、进阶拓展:事务支持
MySQL C 接口同样支持事务操作,通过以下函数实现事务的提交和回滚,适用于需要保证数据一致性的场景(如转账、订单创建等):
函数名称 | 原型 | 功能说明 |
---|---|---|
mysql_autocommit | my_bool mysql_autocommit(MYSQL *mysql, my_bool auto_mode) | 设置自动提交模式(0 关闭自动提交,1 开启) |
mysql_commit | my_bool mysql_commit(MYSQL *mysql) | 提交事务 |
mysql_rollback | my_bool mysql_rollback(MYSQL *mysql) | 回滚事务 |
事务示例代码:
cpp
// 关闭自动提交,开启事务
mysql_autocommit(mysql_conn, 0);
// 执行多个 SQL 操作(如转账:A 减 100,B 加 100)
const char *sql1 = "UPDATE account SET balance = balance - 100 WHERE name = 'A'";
const char *sql2 = "UPDATE account SET balance = balance + 100 WHERE name = 'B'";
if (mysql_query(mysql_conn, sql1) == 0 && mysql_query(mysql_conn, sql2) == 0) {
mysql_commit(mysql_conn); // 两个操作都成功,提交事务
printf("Transaction committed\n");
} else {
mysql_rollback(mysql_conn); // 任一操作失败,回滚事务
fprintf(stderr, "Transaction rolled back: %s\n", mysql_error(mysql_conn));
}
// 恢复自动提交模式
mysql_autocommit(mysql_conn, 1);
五、总结与注意事项
1. 核心流程回顾
C 语言操作 MySQL 的核心是 "句柄初始化 → 连接 → 执行 SQL → 处理结果 → 释放资源" 的闭环流程 ,每个步骤都依赖特定的接口函数,且需严格按顺序执行(如必须先初始化再连接,先释放结果集再关闭连接)。
2. 关键注意事项
- 内存管理:mysql_store_result 分配的结果集必须通过 mysql_free_result 释放,否则会导致内存泄漏;
- 错误处理:每个接口函数调用后都需判断返回值,通过 mysql_error(mysql_conn) 获取具体错误信息,便于调试;
- 字符集:连接成功后务必设置 utf8 字符集,避免中文乱码;
- 动态库路径:运行程序时若提示 "找不到动态库",需通过LD_LIBRARY_PATH(Linux)或DYLD_LIBRARY_PATH(macOS)指定库文件路径。