登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL C API 读取含零字节字段时怎么使用 mysql_fetch_lengths

来源:17golang原创

时间:2026-11-11 04:16:19 469浏览 收藏

读取 MySQL 的 BLOB 或其他可能包含零字节的字段时,正确做法是:先让 mysql_fetch_row() 取得当前行,再调用 mysql_fetch_lengths() 取得每一列的字节数,最后用这个长度复制数据。不要用 strlen() 推断字段大小,因为它会把第一个 \0 当成字符串结尾。

官方地址:https://dev.mysql.com/doc/c-api/8.4/en/mysql-fetch-lengths.html

关键边界是:长度数组只对应当前行;长度不包含结尾的空字节;长度为 0 既可能是空字段,也可能是 SQL NULL,必须再看 row[index] 是否为 NULL。

为什么二进制字段不能交给 strlen

MySQL C API 返回的 MYSQL_ROW 是一组字段指针。文本字段通常以零字节结束,看起来像 C 字符串;但 BLOB、压缩内容、序列化数据或用户主动保存的二进制内容都可能在中间出现 \0。此时 strlen(row[index]) 只能找到第一个零字节,后半段仍在结果缓冲区中,却不会被复制。

mysql_fetch_lengths(result) 返回当前行每列的 unsigned long 长度,单位是字节,而且不包含额外的终止零字节。它还避免了为文本字段再次扫描内存;因此即使字段看起来是字符串,按长度处理也更稳定。

图1把结果集、当前行、长度数组和复制动作放在同一张静态关系图中,重点看 MYSQL_ROW 与 unsigned long 长度之间的对应关系。它是结构说明图,不是运行截图。

MYSQL_RES、mysql_fetch_row、MYSQL_ROW、mysql_fetch_lengths 和 memcpy 之间的静态关系说明图
图1:结构说明图,查看结果集、当前行指针和按字节长度复制之间的关系。

在当前行里用 mysql_fetch_lengths 复制完整字段

调用顺序不要颠倒:必须先成功取得一行,之后长度数组才有明确的当前行。下面的辅助函数复制一个字段到调用方拥有的缓冲区,适合 BLOB 或含嵌入零字节的数据。

#include 
#include 
#include 

/* 返回 1 表示读取成功;out == NULL 且 out_len == 0 表示 SQL NULL。 */
static int copy_binary_field(MYSQL_RES *result, MYSQL_ROW row,
                             unsigned int index, unsigned char **out,
                             unsigned long *out_len)
{
    unsigned long *lengths;
    unsigned int field_count;

    /* 长度数组只对刚刚取得的当前行有效。 */
    lengths = mysql_fetch_lengths(result);
    field_count = mysql_num_fields(result);
    if (lengths == NULL || index >= field_count) {
        return 0; /* 结果无效或列下标越界。 */
    }

    if (row[index] == NULL) {
        *out = NULL;
        *out_len = 0; /* NULL 列和空列都长为 0,要靠指针区分。 */
        return 1;
    }

    *out_len = lengths[index];
    *out = (unsigned char *)malloc(*out_len == 0 ? 1 : *out_len);
    if (*out == NULL) {
        return 0; /* 内存分配失败时不复制半截数据。 */
    }
    if (*out_len != 0) {
        memcpy(*out, row[index], *out_len); /* 按字节数复制,允许中间出现零字节。 */
    }
    return 1;
}

/* 当前行的生命周期结束后,调用方负责释放复制出来的缓冲区。 */
void consume_row(MYSQL_RES *result, unsigned int blob_index)
{
    MYSQL_ROW row = mysql_fetch_row(result);
    unsigned char *data = NULL;
    unsigned long data_len = 0;

    if (row == NULL) {
        return; /* 没有下一行,或使用流式结果时发生了读取错误。 */
    }
    if (!copy_binary_field(result, row, blob_index, &data, &data_len)) {
        return; /* 真实程序应记录错误并走统一清理路径。 */
    }
    if (data != NULL) {
        /* 这里可以把 data[0..data_len) 写入文件或交给二进制解析器。 */
        free(data);
    }
}

这里的 data_len 是字段的真实字节数,memcpy 不会因为中间有零字节而提前停止。复制完成后,数据不再依赖 MySQL 结果集内部的当前行缓冲区,可以在下一次 mysql_fetch_row() 或释放结果集后继续使用。

图2强调的是字段级对应:列下标选中一个指针和一个长度,源字节区按该长度进入应用侧缓冲区。图中只表达静态数据关系,不代表某次真实执行输出。

当前 MYSQL_ROW、列下标、长度数组和应用侧二进制缓冲区的静态关系图
图2:数据关系说明图,查看当前行字段如何按 lengths[index] 复制到应用缓冲区。

空字段、SQL NULL 和当前行生命周期要分开处理

官方 C API 对空字段和 SQL NULL 都返回长度 0,但两者的指针状态不同:空字段的 row[index] 不是 NULL,只是 lengths[index] == 0;SQL NULL 的字段指针才是 NULL。因此不要只依据长度判断是否缺失。

字段状态row[index]lengths[index]处理建议
SQL NULLNULL0按缺失值处理,不读取指针
空字段非 NULL0保留为空字节序列
普通文本非 NULL实际字节数可按长度复制,不依赖 strlen
含零字节的二进制非 NULL完整字节数必须用 memcpy 等长度感知接口

还有三个容易踩到的边界:

  • 只对当前行有效:取得下一行后,上一行的长度数组和字段指针都不应继续当作稳定存储。
  • 不包含终止零字节:如果业务最终要构造 C 字符串,应另行分配 length + 1 并手动补零,不能把补零空间算进数据库字段长度。
  • 调用时机要正确:在 mysql_fetch_row() 之前,或结果集的所有行都读完之后,调用 mysql_fetch_lengths() 都没有可用的当前行。

用这张检查表避免字段被截断

如果代码要把 MySQL 字段保存到文件、消息或自定义协议,按下面顺序检查即可:

  1. 是否在成功取得当前行之后调用了 mysql_fetch_lengths()?
  2. 是否用 lengths[index] 作为复制长度,而不是用 strlen()?
  3. 是否先判断 row[index] == NULL,再读取字段内容?
  4. 是否在下一次取行或释放结果集前,把需要长期保存的数据复制到自己的缓冲区?
  5. 复制后的缓冲区是否在所有分支都释放,且没有把二进制数据当作以零结尾的文本打印?

一句话总结:mysql_fetch_row() 给出当前行的字段指针,mysql_fetch_lengths() 给出这些指针对应的字节长度;只要数据可能包含零字节,就按“指针 + 长度”处理。

相关问题

  • mysql_fetch_lengths 返回的长度包含字符串结尾吗?不包含,长度只表示字段数据本身的字节数。
  • 长度为 0 能说明字段是 NULL 吗?不能,空字段和 SQL NULL 都可能是 0,要检查字段指针。
  • 可以直接保存 row[index] 指针吗?不建议。需要跨越下一次取行或结果集释放后使用时,应按返回长度复制。
声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>