Linux 下基于 mysql.h 的 MySQL C API 实战指南
·
一、环境准备
1.1 安装依赖库
开发前需先安装 MySQL C 开发库(libmysqlclient),不同 Linux 发行版安装命令如下:
# CentOS/RHEL 系统
yum install -y mysql-community-devel
# Ubuntu/Debian 系统
apt-get install -y libmysqlclient-dev
验证安装:检查头文件和库文件是否存在
# 检查头文件(64位系统默认路径)
ls /usr/include/mysql/mysql.h
# 检查库文件(64位系统默认路径)
ls /usr/lib64/libmysqlclient.so
1.2 编译链接说明
使用mysql.h编写的程序,编译时需指定 MySQL 客户端库,核心编译参数说明:
-I:指定mysql.h头文件路径(默认在系统路径可省略);-lmysqlclient:链接 MySQL 客户端库(核心参数,不可省略);-L:指定库文件路径(库文件不在默认路径时需补充)。
示例编译命令:
gcc mysql_demo.c -o mysql_demo -lmysqlclient
二、MySQL 执行 SQL 语句的核心流程(分场景对比)

三、MySQL 执行 SQL 的两种核心方式
MySQL C API 提供两种核心 SQL 执行方式,分别适配「静态固定 SQL」和「动态参数 / 二进制数据」场景,以下是详细对比与用法。
3.1 静态 SQL 直接执行(无参数填充)
核心逻辑
SQL 语句在代码中完全写死(或拼接为完整字符串),直接调用mysql_query/mysql_real_query执行,无需参数绑定,适合无动态参数的固定逻辑场景。
核心函数对比
| 函数 | 适用场景 | 核心特点 |
|---|---|---|
mysql_query |
SQL 无二进制 / 特殊字符(如\0) |
传入以\0结尾的字符串,内部自动计算长度,使用简单 |
mysql_real_query |
SQL 含二进制 / 特殊字符(如\0) |
手动指定 SQL 长度,避免\0截断,安全性更高(推荐使用) |
代码示例
// 定义静态SQL(完全写死)
#define SQL_INSERT_TBL_USER "INSERT TBL_USER(U_NAME,U_GENDER) VALUES('wangqing','man');"
// 执行静态SQL(推荐使用mysql_real_query)
mysql_real_query(mysql, SQL_INSERT_TBL_USER, strlen(SQL_INSERT_TBL_USER));
优缺点与适用场景
| 优点 | 缺点 | 适用场景 |
|---|---|---|
| 代码简单,无需额外参数绑定 | 易引发 SQL 注入(拼接参数时) | 固定逻辑的 SQL(无动态参数) |
| 执行流程短,少量 SQL 效率高 | 无法处理二进制数据(如 BLOB) | 简单查询 / 插入(如固定值插入、全表查询) |
3.2 动态参数填充(预处理语句,Prepared Statement)
核心逻辑
先定义带「占位符?」的模板 SQL,再通过参数绑定填充动态值,最后执行。是处理「动态参数」「二进制数据(BLOB/TEXT)」的标准方式,也是防 SQL 注入的最佳实践。
核心执行流程

核心函数(预处理专用)
| 函数 | 核心作用 |
|---|---|
mysql_stmt_init |
初始化预处理语句句柄(返回MYSQL_STMT*类型) |
mysql_stmt_prepare |
解析带占位符的模板 SQL(仅解析一次,重复执行效率高) |
mysql_stmt_bind_param |
绑定参数到占位符(指定参数类型 + 数据地址) |
mysql_stmt_send_long_data |
给 BLOB/TEXT 分块发送大数据(避免大文件内存溢出) |
mysql_stmt_execute |
执行绑定参数后的预处理语句 |
mysql_stmt_bind_result |
读取结果时,绑定接收数据的缓冲区(如读取 BLOB 数据) |
代码示例(BLOB 数据写入)
// 定义带占位符的模板SQL(BLOB字段用?占位)
#define SQL_INSERT_IMG_USER "INSERT TBL_USER(U_NAME,U_GENDER,U_IMG) VALUES('KING','MAN',?);"
// 1. 初始化预处理句柄
MYSQL_STMT *stmt = mysql_stmt_init(handle);
// 2. 预处理模板SQL
mysql_stmt_prepare(stmt, SQL_INSERT_IMG_USER, strlen(SQL_INSERT_IMG_USER));
// 3. 绑定BLOB参数
MYSQL_BIND param = {0};
param.buffer_type = MYSQL_TYPE_LONG_BLOB; // 指定类型为长二进制
// 4. 分块发送BLOB数据
mysql_stmt_send_long_data(stmt, 0, buffer, length);
// 5. 执行预处理语句
mysql_stmt_execute(stmt);
优缺点与适用场景
| 优点 | 缺点 | 适用场景 |
|---|---|---|
| 防 SQL 注入(参数与 SQL 分离) | 代码稍复杂,多步操作 | 带动态参数的 SQL(如用户输入的查询条件) |
| 支持二进制数据(BLOB/TEXT) | 首次执行需预处理,略慢 | 大二进制数据读写(图片、文件) |
| 重复执行(批量操作)效率高 | - | 批量插入 / 更新(预处理一次,多次绑定参数) |
四、不同数据类型的读写差异
MySQL 数据类型可分为「普通类型(文本 / 数值)」和「二进制大对象(BLOB)」两大类,读写逻辑的核心差异在于「数据存储形式」和「内存处理方式」。
4.1 普通数据类型(VARCHAR/INT/CHAR 等)
存储特点
- 以「文本 / 字符」形式存储(即使是 INT 类型,C API 返回的也是字符串);
- 数据量小,可一次性加载到内存,无需分块处理。
读写核心逻辑
- 写入:直接拼接为静态 SQL,或通过预处理绑定
MYSQL_TYPE_STRING/MYSQL_TYPE_LONG等类型; - 读取:通过
mysql_fetch_row获取MYSQL_ROW(char**类型),直接打印或转换为对应数值类型。
代码示例(读取 INT 类型字段)
// 读取普通数据行
MYSQL_ROW row = mysql_fetch_row(res);
// 将字符串类型的age字段转换为INT
int age = atoi(row[2]);
printf("年龄:%d\n", age);
4.2 BLOB 二进制大对象类型(TINYBLOB/BLOB/MEDIUMBLOB/LONG_BLOB)
存储特点
- 以「原始二进制」形式存储(如图片字节流、文件二进制数据);
- 数据量可能很大(如几 MB / 几十 MB),无法直接拼接为字符串 SQL。
读写核心逻辑(必须用预处理语句)
| 操作 | 核心步骤 |
|---|---|
| 写入 | 1. 模板 SQL 带?占位符;2. 绑定MYSQL_TYPE_LONG_BLOB类型;3. mysql_stmt_send_long_data分块发送数据;4. 执行语句 |
| 读取 | 1. 模板 SQL 查询 BLOB 字段;2. 绑定接收缓冲区;3. mysql_stmt_fetch获取数据长度;4. mysql_stmt_fetch_column分块读取 |
关键注意事项
- 读写图片文件时,必须用二进制模式(
fopen的rb/wb+),否则会篡改二进制数据; - 读取 BLOB 时需先获取实际长度(
result.length),避免缓冲区溢出; - 大 BLOB(如几十 MB)需分块读取,不可一次性分配超大缓冲区。
五、完整实战代码
以下是整合「静态 SQL 操作普通数据」「预处理 SQL 操作 BLOB 数据」的完整可运行代码,包含详细注释与错误处理:
#include <stdio.h>
#include <string.h>
#include <mysql.h>
// ===================== 数据库配置常量 =====================
#define KING_DB_SERVER_IP "192.168.199.134" // MySQL服务器IP
#define KING_DB_SERVER_PORT 3306 // MySQL端口
#define KING_DB_USERNAME "admin" // 登录用户名
#define KING_DB_PASSWORD "123456" // 登录密码
#define KING_DB_DEFAULTDB "KING_DB" // 默认连接的数据库名
// ===================== 静态SQL(无动态参数/普通数据) =====================
#define SQL_INSERT_TBL_USER "INSERT TBL_USER(U_NAME, U_GENDER) VALUES('King', 'man');"
#define SQL_SELECT_TBL_USER "SELECT * FROM TBL_USER;"
#define SQL_DELETE_TBL_USER "CALL PROC_DELETE_USER('King')"
// ===================== 预处理SQL(动态参数/BLOB数据) =====================
#define SQL_INSERT_IMG_USER "INSERT TBL_USER(U_NAME, U_GENDER, U_IMG) VALUES('King', 'man', ?);"
#define SQL_SELECT_IMG_USER "SELECT U_IMG FROM TBL_USER WHERE U_NAME='King';"
// 图片缓冲区大小(64KB)
#define FILE_IMAGE_LENGTH (64*1024)
// ===================== 工具函数:错误处理宏 =====================
#define CHECK_MYSQL_ERR(handle, ret, msg) \
do { \
if (ret) { \
printf("%s : %s\n", msg, mysql_error(handle)); \
return -__LINE__; /* 返回行号,方便定位错误 */ \
} \
} while(0)
// ===================== 功能1:静态SQL执行 - 查询普通数据 =====================
/**
* @brief 执行静态SELECT SQL,读取普通数据(VARCHAR/INT等)并打印
* @param handle MySQL连接句柄
* @return 0成功,非0失败(错误码对应行号)
*/
int king_mysql_select(MYSQL *handle) {
// 1. 执行静态SQL
int ret = mysql_real_query(handle, SQL_SELECT_TBL_USER, strlen(SQL_SELECT_TBL_USER));
CHECK_MYSQL_ERR(handle, ret, "mysql_real_query [SELECT普通数据]");
// 2. 获取查询结果集
MYSQL_RES *res = mysql_store_result(handle);
if (res == NULL) {
printf("mysql_store_result : %s\n", mysql_error(handle));
return -__LINE__;
}
// 3. 获取结果集元信息
int rows = mysql_num_rows(res); // 结果集行数
printf("【普通数据查询】结果行数: %d\n", rows);
int fields = mysql_num_fields(res); // 结果集列数
printf("【普通数据查询】结果列数: %d\n", fields);
// 4. 逐行读取普通数据
MYSQL_ROW row;
printf("【普通数据查询】结果内容:\n");
while ((row = mysql_fetch_row(res))) {
for (int i = 0; i < fields; i++) {
printf("%s\t", row[i] ? row[i] : "NULL"); // 处理NULL值
}
printf("\n");
}
// 5. 释放结果集
mysql_free_result(res);
return 0;
}
// ===================== 工具函数:读取本地图片文件(二进制) =====================
/**
* @brief 以二进制模式读取本地图片文件到缓冲区
* @param filename 图片文件路径
* @param buffer 接收图片数据的缓冲区
* @return 成功返回文件大小,失败返回负数
*/
int read_image(char *filename, char *buffer) {
if (filename == NULL || buffer == NULL) return -1;
// 二进制模式打开文件
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
printf("read_image fopen failed: %s\n", filename);
return -2;
}
// 获取文件大小
fseek(fp, 0, SEEK_END);
int length = ftell(fp);
fseek(fp, 0, SEEK_SET);
// 读取二进制数据到缓冲区
int size = fread(buffer, 1, length, fp);
if (size != length) {
printf("read_image fread failed: 实际读取%d字节,期望%d字节\n", size, length);
fclose(fp);
return -3;
}
fclose(fp);
printf("【读取图片】成功读取%s,大小:%d字节\n", filename, size);
return size;
}
// ===================== 工具函数:写入图片文件(二进制) =====================
/**
* @brief 以二进制模式将缓冲区数据写入图片文件
* @param filename 输出图片路径
* @param buffer 图片二进制数据缓冲区
* @param length 图片数据长度
* @return 成功返回写入大小,失败返回负数
*/
int write_image(char *filename, char *buffer, int length) {
if (filename == NULL || buffer == NULL || length <= 0) return -1;
// 二进制模式写入文件
FILE *fp = fopen(filename, "wb+");
if (fp == NULL) {
printf("write_image fopen failed: %s\n", filename);
return -2;
}
// 写入二进制数据
int size = fwrite(buffer, 1, length, fp);
if (size != length) {
printf("write_image fwrite failed: 实际写入%d字节,期望%d字节\n", size, length);
fclose(fp);
return -3;
}
fclose(fp);
printf("【写入图片】成功写入%s,大小:%d字节\n", filename, size);
return size;
}
// ===================== 功能2:预处理SQL执行 - 写入BLOB数据 =====================
/**
* @brief 执行预处理SQL,将图片二进制数据写入MySQL的LONG_BLOB字段
* @param handle MySQL连接句柄
* @param buffer 图片二进制数据缓冲区
* @param length 图片数据长度
* @return 0成功,非0失败
*/
int mysql_write(MYSQL *handle, char *buffer, int length) {
if (handle == NULL || buffer == NULL || length <= 0) return -1;
// 1. 初始化预处理句柄
MYSQL_STMT *stmt = mysql_stmt_init(handle);
if (stmt == NULL) {
printf("mysql_stmt_init failed\n");
return -__LINE__;
}
// 2. 预处理模板SQL
int ret = mysql_stmt_prepare(stmt, SQL_INSERT_IMG_USER, strlen(SQL_INSERT_IMG_USER));
CHECK_MYSQL_ERR(handle, ret, "mysql_stmt_prepare [插入BLOB]");
// 3. 绑定BLOB参数
MYSQL_BIND param = {0};
param.buffer_type = MYSQL_TYPE_LONG_BLOB;
param.buffer = NULL;
param.is_null = 0;
param.length = NULL;
ret = mysql_stmt_bind_param(stmt, ¶m);
CHECK_MYSQL_ERR(handle, ret, "mysql_stmt_bind_param [插入BLOB]");
// 4. 分块发送BLOB数据
ret = mysql_stmt_send_long_data(stmt, 0, buffer, length);
CHECK_MYSQL_ERR(handle, ret, "mysql_stmt_send_long_data [插入BLOB]");
// 5. 执行预处理语句
ret = mysql_stmt_execute(stmt);
CHECK_MYSQL_ERR(handle, ret, "mysql_stmt_execute [插入BLOB]");
// 6. 释放预处理句柄
ret = mysql_stmt_close(stmt);
CHECK_MYSQL_ERR(handle, ret, "mysql_stmt_close [插入BLOB]");
printf("【插入BLOB】图片数据写入数据库成功\n");
return 0;
}
// ===================== 功能3:预处理SQL执行 - 读取BLOB数据 =====================
/**
* @brief 执行预处理SQL,从MySQL的LONG_BLOB字段读取图片二进制数据
* @param handle MySQL连接句柄
* @param buffer 接收图片数据的缓冲区
* @param length 缓冲区最大长度
* @return 成功返回图片实际长度,失败返回负数
*/
int mysql_read(MYSQL *handle, char *buffer, int length) {
if (handle == NULL || buffer == NULL || length <= 0) return -1;
// 1. 初始化预处理句柄
MYSQL_STMT *stmt = mysql_stmt_init(handle);
if (stmt == NULL) {
printf("mysql_stmt_init failed\n");
return -__LINE__;
}
// 2. 预处理模板SQL
int ret = mysql_stmt_prepare(stmt, SQL_SELECT_IMG_USER, strlen(SQL_SELECT_IMG_USER));
CHECK_MYSQL_ERR(handle, ret, "mysql_stmt_prepare [查询BLOB]");
// 3. 绑定结果集
MYSQL_BIND result = {0};
result.buffer_type = MYSQL_TYPE_LONG_BLOB;
unsigned long total_length = 0;
result.length = &total_length;
result.buffer = buffer;
result.buffer_length = length;
ret = mysql_stmt_bind_result(stmt, &result);
CHECK_MYSQL_ERR(handle, ret, "mysql_stmt_bind_result [查询BLOB]");
// 4. 执行预处理查询
ret = mysql_stmt_execute(stmt);
CHECK_MYSQL_ERR(handle, ret, "mysql_stmt_execute [查询BLOB]");
// 5. 存储结果集
ret = mysql_stmt_store_result(stmt);
CHECK_MYSQL_ERR(handle, ret, "mysql_stmt_store_result [查询BLOB]");
// 6. 读取BLOB数据
ret = mysql_stmt_fetch(stmt);
if (ret != 0 && ret != MYSQL_DATA_TRUNCATED) {
printf("mysql_stmt_fetch [查询BLOB] : %s\n", mysql_error(handle));
mysql_stmt_close(stmt);
return -__LINE__;
}
// 校验缓冲区是否足够
if (total_length > (unsigned long)length) {
printf("【查询BLOB】缓冲区不足:需要%lu字节,实际%d字节\n", total_length, length);
mysql_stmt_close(stmt);
return -__LINE__;
}
// 7. 分块读取BLOB数据到缓冲区
int start = 0;
while (start < (int)total_length) {
result.buffer = buffer + start;
result.buffer_length = length - start;
mysql_stmt_fetch_column(stmt, &result, 0, start);
start += result.buffer_length;
}
// 8. 释放资源
mysql_stmt_close(stmt);
printf("【查询BLOB】成功读取图片数据,长度:%lu字节\n", total_length);
return total_length;
}
// ===================== 主函数:完整流程演示 =====================
int main() {
// 1. 初始化MySQL连接句柄
MYSQL mysql;
if (NULL == mysql_init(&mysql)) {
printf("mysql_init failed : %s\n", mysql_error(&mysql));
return -1;
}
// 2. 建立数据库连接
if (!mysql_real_connect(&mysql, KING_DB_SERVER_IP, KING_DB_USERNAME,
KING_DB_PASSWORD, KING_DB_DEFAULTDB, KING_DB_SERVER_PORT, NULL, 0)) {
printf("mysql_real_connect failed : %s\n", mysql_error(&mysql));
goto Exit;
}
// 设置字符集,避免中文乱码
mysql_set_character_set(&mysql, "utf8");
printf("【数据库连接】成功连接到%s:%d\n", KING_DB_SERVER_IP, KING_DB_SERVER_PORT);
// ===================== 场景1:静态SQL - 插入普通数据 =====================
printf("\n========== 场景1:静态SQL执行(普通数据插入) ==========\n");
#if 1
int ret = mysql_real_query(&mysql, SQL_INSERT_TBL_USER, strlen(SQL_INSERT_TBL_USER));
if (ret) {
printf("mysql_real_query [插入普通数据] : %s\n", mysql_error(&mysql));
goto Exit;
}
printf("【静态SQL】普通数据插入成功\n");
#endif
// ===================== 场景2:静态SQL - 查询普通数据 =====================
printf("\n========== 场景2:静态SQL执行(普通数据查询) ==========\n");
king_mysql_select(&mysql);
// ===================== 场景3:静态SQL - 调用存储过程删除数据 =====================
printf("\n========== 场景3:静态SQL执行(调用存储过程) ==========\n");
#if 1
ret = mysql_real_query(&mysql, SQL_DELETE_TBL_USER, strlen(SQL_DELETE_TBL_USER));
if (ret) {
printf("mysql_real_query [调用存储过程] : %s\n", mysql_error(&mysql));
goto Exit;
}
printf("【静态SQL】存储过程调用成功(删除数据)\n");
#endif
king_mysql_select(&mysql); // 验证删除结果
// ===================== 场景4:预处理SQL - 写入BLOB数据 =====================
printf("\n========== 场景4:预处理SQL执行(BLOB图片写入) ==========\n");
char buffer[FILE_IMAGE_LENGTH] = {0};
int img_len = read_image("0voice.jpg", buffer);
if (img_len < 0) {
printf("读取本地图片失败,错误码:%d\n", img_len);
goto Exit;
}
ret = mysql_write(&mysql, buffer, img_len);
if (ret != 0) {
printf("写入BLOB数据失败,错误码:%d\n", ret);
goto Exit;
}
// ===================== 场景5:预处理SQL - 读取BLOB数据 =====================
printf("\n========== 场景5:预处理SQL执行(BLOB图片读取) ==========\n");
memset(buffer, 0, FILE_IMAGE_LENGTH);
img_len = mysql_read(&mysql, buffer, FILE_IMAGE_LENGTH);
if (img_len < 0) {
printf("读取BLOB数据失败,错误码:%d\n", img_len);
goto Exit;
}
write_image("a.jpg", buffer, img_len);
Exit:
// 3. 关闭数据库连接
mysql_close(&mysql);
printf("\n【程序结束】数据库连接已关闭\n");
return 0;
}
六、核心对比与实战建议
6.1 核心方式对比表
| 对比维度 | 静态 SQL 直接执行 | 预处理语句(动态参数) |
|---|---|---|
| 参数处理 | 静态拼接,易注入 | 参数绑定,防注入 |
| 数据类型支持 | 仅普通类型(文本 / 数值) | 支持所有类型(含 BLOB/TEXT) |
| 性能 | 单次执行快 | 重复执行(批量)快 |
| 代码复杂度 | 低 | 高(多步绑定 / 释放) |
| 典型应用场景 | 固定逻辑查询 / 插入 | 动态参数操作、BLOB 数据读写 |
6.2 实战建议
- 普通数据(姓名 / 性别 / 年龄):简单场景用静态 SQL,涉及用户输入的动态参数场景必须用预处理;
- BLOB 数据(图片 / 文件):强制使用预处理语句,通过
mysql_stmt_send_long_data分块发送,避免内存溢出; - 防 SQL 注入:只要涉及「用户输入的动态参数」,无论数据类型,一律使用预处理语句;
- 资源释放:预处理语句执行后必须调用
mysql_stmt_close,查询结果集必须调用mysql_free_result,避免内存泄漏; - 字符集设置:连接成功后调用
mysql_set_character_set(&mysql, "utf8"),避免中文乱码。
更多推荐




所有评论(0)