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

MySQL C API 怎么用 mysql_set_server_option 动态开启多语句

来源:17golang原创

时间:2026-11-11 04:16:51 490浏览 收藏

如果 MySQL C API 连接已经建立,但只有某个业务分支临时需要执行由分号分隔的多条 SQL,可以调用 mysql_set_server_option 动态打开多语句支持。成功的关键不只是打开开关,还要把每条语句产生的结果集或影响行数逐个取完。

官方地址:https://dev.mysql.com/doc/c-api/9.7/en/mysql-set-server-option.html

连接成功后调用 mysql_set_server_option(mysql, MYSQL_OPTION_MULTI_STATEMENTS_ON)。返回值为 0 才算开启成功;随后每次执行多语句,都要用 mysql_store_result、mysql_affected_rows 和 mysql_next_result 把结果链消费完。

先分清三个容易混淆的能力

MySQL C API 把“能不能在一个字符串中发送多条语句”和“能不能继续读取多个结果”分成了相关但不完全相同的能力:

  • MYSQL_OPTION_MULTI_STATEMENTS_ON:在连接建立后打开多语句执行。
  • MYSQL_OPTION_MULTI_STATEMENTS_OFF:关闭这项动态开关。
  • CLIENT_MULTI_RESULTS:允许客户端处理多个结果,存储过程 CALL 返回结果集时尤其重要。

动态开启多语句适合普通的多条 SELECT、INSERT 或 UPDATE 组合。官方文档特别提醒:只调用 MYSQL_OPTION_MULTI_STATEMENTS_ON,并不等价于同时打开完整的多结果能力;如果要执行可能返回多个结果集的 CALL,连接阶段应使用 CLIENT_MULTI_STATEMENTS,它会连带启用 CLIENT_MULTI_RESULTS。

MySQL C API 连接开关、查询调用和多结果处理之间的静态关系图
图1:连接能力、查询入口与结果处理之间的静态关系示意图,不是终端或数据库控制台截图。

连接建立后怎样打开多语句

把开关调用放在 mysql_real_connect 成功之后。函数返回 0 表示成功,非零表示失败;失败时应立即读取 mysql_error,不要继续把当前连接当作已开启多语句的连接使用。

#include 
#include 

int enable_multi_statements(MYSQL *mysql) {
    // 连接必须已经成功建立,动态选项依附于这条连接。
    if (mysql_set_server_option(mysql, MYSQL_OPTION_MULTI_STATEMENTS_ON) != 0) {
        // 非零表示服务端或客户端拒绝了这个选项,保留真实错误便于上层处理。
        fprintf(stderr, "开启多语句失败: %s\\n", mysql_error(mysql));
        return 0;
    }

    // 返回 0 才表示开关已经生效。
    return 1;
}

这里的函数只负责切换能力,不负责发送 SQL。把开关和查询拆开,调用方可以在连接池借出连接后按业务需要打开,完成本次使用,再在归还前关闭。

当前结果既可能是结果集,也可能只有影响行数

多语句字符串中的每一条 SQL 都会产生一个当前结果。mysql_store_result 返回非空指针时,说明当前结果是结果集;如果返回空指针,还要区分“当前语句没有结果集”和“当前语句本身出错”。可用 mysql_field_count 辅助判断。

static int consume_current_result(MYSQL *mysql) {
    MYSQL_RES *result = mysql_store_result(mysql);

    if (result != NULL) {
        // 结果集需要逐行读取;示例只展示消费动作,不把列格式写死。
        MYSQL_ROW row;
        while ((row = mysql_fetch_row(result)) != NULL) {
            // 真实项目在这里映射列值,并处理 NULL 列。
        }
        // 释放当前结果,否则下一次推进可能造成命令不同步。
        mysql_free_result(result);
        return 1;
    }

    if (mysql_field_count(mysql) == 0) {
        // 没有结果集时读取影响行数,适用于 INSERT、UPDATE 等语句。
        printf("影响行数: %llu\\n",
               (unsigned long long) mysql_affected_rows(mysql));
        return 1;
    }

    // 有字段却拿不到结果集,通常表示当前语句发生了错误。
    fprintf(stderr, "读取结果失败: %s\\n", mysql_error(mysql));
    return 0;
}

不要把 mysql_store_result 返回空指针一律当成成功。没有字段的写操作确实没有结果集,但有字段的语句拿不到结果时,应当保留错误并停止继续消费。

用 mysql_next_result 逐个推进结果链

发送多语句后,先处理第一条语句的当前结果,再调用 mysql_next_result。返回 0 表示成功推进且还有下一个结果,返回 -1 表示已经没有更多结果,大于 0 表示推进时出错。

static int execute_multi(MYSQL *mysql, const char *sql) {
    if (mysql_query(mysql, sql) != 0) {
        // 首条语句发送失败时,不进入结果消费循环。
        fprintf(stderr, "执行多语句失败: %s\\n", mysql_error(mysql));
        return 0;
    }

    for (;;) {
        // 每轮先处理当前语句留下的结果集或影响行数。
        if (!consume_current_result(mysql)) {
            return 0;
        }

        // 0 表示还有结果,-1 表示正常结束,大于 0 表示推进错误。
        int next = mysql_next_result(mysql);
        if (next == -1) {
            return 1;
        }
        if (next != 0) {
            fprintf(stderr, "读取下一个结果失败: %s\\n", mysql_error(mysql));
            return 0;
        }
    }
}

以 SELECT ...; UPDATE ...; SELECT ... 为例,循环会依次看到第一个结果集、一次影响行数和第二个结果集。只处理第一个 mysql_store_result 就返回,会让连接上还留着未消费的结果,下一次命令可能得到 CR_COMMANDS_OUT_OF_SYNC,甚至导致连接被丢弃。

MySQL 多语句结果集和影响行数在同一连接上的对象关系图
图2:当前结果、结果集释放、影响行数与下一个结果之间的静态关系示意图,不是运行结果截图。

什么时候关闭动态开关

如果连接要回到只接受单条语句的默认使用方式,可以在本次批量操作结束后关闭:

int disable_multi_statements(MYSQL *mysql) {
    // 只有上一轮结果全部消费后才能安全切换连接状态。
    if (mysql_set_server_option(mysql, MYSQL_OPTION_MULTI_STATEMENTS_OFF) != 0) {
        // 关闭失败也要把连接视为状态不确定,交给连接池决定是否淘汰。
        fprintf(stderr, "关闭多语句失败: %s\\n", mysql_error(mysql));
        return 0;
    }
    return 1;
}

连接池场景尤其要避免“借出时打开、归还时忘记恢复”。如果后续调用方不希望接受多语句,最稳妥的做法是把开关状态作为连接包装器的一部分管理,或者在归还前明确关闭并记录失败。

动态开启和连接参数怎么选

场景建议原因
偶尔在已连接会话中执行普通多语句调用 mysql_set_server_option不必改变连接建立参数
整个连接生命周期都需要多语句和多结果连接时传 CLIENT_MULTI_STATEMENTS同时启用多语句与多结果处理
存储过程可能返回多个结果集确保 CLIENT_MULTI_RESULTS 已启用动态开启多语句本身不够
使用预处理语句接口不要把多条语句拼在一个字符串中预处理接口按单条语句工作

几个容易漏掉的检查点

  • 开关调用必须发生在连接成功之后,且返回值为 0 才算成功。
  • 每一次多语句调用都要处理结果集、影响行数和 mysql_next_result 返回值。
  • 调用 mysql_next_result 前,要释放当前结果集。
  • 遇到错误时不要继续盲目推进;先记录 mysql_error,再决定复用还是淘汰连接。
  • 开关只解决执行能力,不会替代参数绑定、输入约束和事务设计。

相关问题

mysql_set_server_option 返回非零怎么办?先读取 mysql_error。常见原因包括调用顺序不对、连接已断开、服务端不支持该选项或当前连接状态不允许继续发命令。

只执行一条 SELECT 也要调用 mysql_next_result 吗?如果连接开启了多结果处理,统一使用结果消费循环更安全;单条语句通常会在第一次推进时得到“没有更多结果”。

为什么 CALL 不能只打开 MYSQL_OPTION_MULTI_STATEMENTS_ON?存储过程可能返回多个结果集以及最终状态结果,需要 CLIENT_MULTI_RESULTS;连接时使用 CLIENT_MULTI_STATEMENTS 会连带启用它。

记住三点即可:连接后用 MYSQL_OPTION_MULTI_STATEMENTS_ON 动态打开,返回 0 才继续;每个结果先判断结果集还是影响行数;最后用 mysql_next_result 把结果链消费到结束。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>