在项目中,我们经常需要将图像等二进制大对象(Binary Large OBject,简称 BLOB)存储到数据库中,之后再读取出来进行处理。本文将以一个完整的 C 语言 MySQL 客户端代码为例,重点讲解如何 从 MySQL 中读取 BLOB 图像数据,并附上实际的 mysql_read 函数实现与剖析

 一、场景概述

我们有如下数据库表结构(简化):

CREATE TABLE TBL_USER (
    U_ID INT AUTO_INCREMENT PRIMARY KEY,
    U_NAME VARCHAR(100),
    U_GENGDER VARCHAR(10),
    U_IMG LONGBLOB
);

通过 SQL 语句:

SELECT U_IMG FROM TBL_USER WHERE U_NAME = 'charon';

我们希望从表中读取用户 charon 的图像数据 U_IMG 字段并保存为文件或进行其他处理。

二、mysql_read 函数实现

int mysql_read(MYSQL *handle, char *buffer, int length) {
    // 输入参数有效性检查
    if (handle == NULL || buffer == NULL || length <= 0) return -1;

    // 初始化预处理语句
    MYSQL_STMT *stmt = mysql_stmt_init(handle);
    int ret = mysql_stmt_prepare(stmt, SQL_SELECT_IMG_USER, strlen(SQL_SELECT_IMG_USER));
    if (ret) {
        printf("mysql_stmt_prepare : %s\n", mysql_error(handle));
        return -2;
    }

    // 绑定结果集参数
    MYSQL_BIND result = {0};
    result.buffer_type = MYSQL_TYPE_LONG_BLOB;  // 设置为长二进制类型
    unsigned long total_length = 0;             // 存储 BLOB 总长度
    result.length = &total_length;              // 指向长度变量的指针

    // 绑定结果集
    ret = mysql_stmt_bind_result(stmt, &result);
    if (ret) {
        printf("mysql_stmt_bind_result : %s\n", mysql_error(handle));
        return -3;
    }

    // 执行查询
    ret = mysql_stmt_execute(stmt);
    if (ret) {
        printf("mysql_stmt_execute : %s\n", mysql_error(handle));
        return -4;
    }

    // 存储结果集(将结果从服务器传输到客户端)
    ret = mysql_stmt_store_result(stmt);
    if (ret) {
        printf("mysql_stmt_store_result : %s\n", mysql_error(handle));
        return -5;
    }

    // 循环读取 BLOB 数据(处理可能的分段情况)
    while (1) {
        // 读取一行数据
        ret = mysql_stmt_fetch(stmt);
        // 当没有更多数据或发生错误时退出循环(排除数据截断情况)
        if (ret != 0 && ret != MYSQL_DATA_TRUNCATED) break;

        // 逐字节读取 BLOB 数据到缓冲区
        int start = 0;
        while (start < (int)total_length) {
            // 设置当前读取位置
            result.buffer = buffer + start;
            // 每次读取 1 字节
            result.buffer_length = 1;
            // 读取一个字节到缓冲区
            mysql_stmt_fetch_column(stmt, &result, 0, start);
            // 更新已读取位置
            start += result.buffer_length;
        }
    }

    // 释放预处理语句资源
    mysql_stmt_close(stmt);
    // 返回实际读取的字节数
    return total_length;
}

三、实现细节解析

1.MYSQL_STMT 预处理语句

MYSQL_STMT *stmt = mysql_stmt_init(handle);
mysql_stmt_prepare(stmt, SQL_SELECT_IMG_USER, strlen(SQL_SELECT_IMG_USER));

使用 mysql_stmt_prepare 预编译带问号参数的 SQL(不过我们这个语句不带参数)。

2.MYSQL_BIND 结构体设置

    // 绑定结果集参数
    MYSQL_BIND result = {0};
    result.buffer_type = MYSQL_TYPE_LONG_BLOB;  // 设置为长二进制类型
    unsigned long total_length = 0;             // 存储 BLOB 总长度
    result.length = &total_length;              // 指向长度变量的指针

通过设置 buffer_typeMYSQL_TYPE_LONG_BLOB,告知 MySQL 我们期望获取的是大字段,同时用 result.length = &total_length; 让 MySQL 能够把实际读取到的数据长度写入 total_length 变量,方便后续处理数据长度。这是为了正确接收和处理数据库中存储的二进制数据(如图片等)。

3.执行查询并存储结果

mysql_stmt_execute(stmt);
mysql_stmt_store_result(stmt);

mysql_stmt_execute() 是告诉服务器:“把饭做好”;
mysql_stmt_store_result() 是告诉服务员:“把饭打包给我”;
之后你就可以一口一口自己吃了。

store_result 会为大对象读取预分配空间。所以 mysql_stmt_store_result() 就是:

“让你一次性把服务器的数据打包拉回来”,
后面你就可以 安心地从本地 一块块读图像、读数据,不担心连不上服务器或者中途出问题。

4.分段读取 BLOB 数据

int start = 0;
while(start < (int)total_length){
    result.buffer = buffer + start;
    result.buffer_length = 1;
    mysql_stmt_fetch_column(stmt,&result,0,&start);
    start += result.buffer_length;
}

MySQL C API 对于大对象不能一次性全部加载到 buffer。你必须通过 mysql_stmt_fetch_column() 进行分段读取。

这里我们每次读取 1 字节,确保安全。你可以将 result.buffer_length 设为更大的值(如 512 或 4096)来提升性能,但需要小心避免越界。

5.返回数据长度

return total_length;

函数最终返回从数据库中读取的 BLOB 数据总字节数。

https://github.com/0voice

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐