一、环境准备

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_ROWchar**类型),直接打印或转换为对应数值类型。
代码示例(读取 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分块读取
关键注意事项
  • 读写图片文件时,必须用二进制模式(fopenrb/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, &param);
    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 实战建议

  1. 普通数据(姓名 / 性别 / 年龄):简单场景用静态 SQL,涉及用户输入的动态参数场景必须用预处理;
  2. BLOB 数据(图片 / 文件):强制使用预处理语句,通过mysql_stmt_send_long_data分块发送,避免内存溢出;
  3. 防 SQL 注入:只要涉及「用户输入的动态参数」,无论数据类型,一律使用预处理语句;
  4. 资源释放:预处理语句执行后必须调用mysql_stmt_close,查询结果集必须调用mysql_free_result,避免内存泄漏;
  5. 字符集设置:连接成功后调用mysql_set_character_set(&mysql, "utf8"),避免中文乱码。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐