3.6.3 多语句执行支持

默认情况下,mysql_real_query()mysql_query() 将其语句字符串参数解释为要执行的单个语句,并且您根据语句是否产生结果集(一组行,如 SELECT)或受影响的行计数(如 INSERTUPDATE 等)来处理结果。

MySQL 还支持执行包含多个由分号 (;) 字符分隔的语句的字符串。此功能通过特殊选项启用,这些选项在您使用 mysql_real_connect() 连接到服务器时或连接后通过调用 mysql_set_server_option() 指定。

执行多语句字符串可能会产生多个结果集或行计数指示器。处理这些结果涉及与单语句情况不同的方法:在处理完第一个语句的结果后,需要检查是否还有更多结果,如果有,则依次处理它们。为了支持多结果处理,C API 包含了 mysql_more_results()mysql_next_result() 函数。这些函数在循环的末尾使用,该循环只要有更多结果就一直迭代。如果不以这种方式处理结果,可能会导致与服务器的连接断开。

如果执行存储过程的 CALL 语句,也需要进行多结果处理。存储过程的结果具有以下特征

  • 过程中的语句可能会产生结果集(例如,如果它执行 SELECT 语句)。这些结果集按照过程执行时产生的顺序返回。

    通常,调用者无法知道过程将返回多少结果集。过程执行可能取决于循环或条件语句,这些语句会导致执行路径在每次调用之间有所不同。因此,您必须准备好检索多个结果。

  • 来自过程的最终结果是一个状态结果,其中不包含结果集。状态指示过程是成功还是发生了错误。

多语句和结果功能只能与 mysql_real_query()mysql_query() 一起使用。它们不能与预处理语句接口一起使用。预处理语句句柄被定义为仅与包含单个语句的字符串一起使用。参见 第 6 章, C API 预处理语句接口

要启用多语句执行和结果处理,可以使用以下选项

  • mysql_real_connect() 函数有一个 flags 参数,其中两个选项值相关

    • CLIENT_MULTI_RESULTS 使客户端程序能够处理多个结果。如果执行产生结果集的存储过程的 CALL 语句,此选项必须启用。否则,此类过程会导致错误 Error 1312 (0A000): PROCEDURE proc_name can't return a result set in the given context。默认情况下,CLIENT_MULTI_RESULTS 已启用。

    • CLIENT_MULTI_STATEMENTS 使 mysql_real_query()mysql_query() 能够执行包含多个由分号分隔的语句的语句字符串。此选项还会隐式启用 CLIENT_MULTI_RESULTS,因此向 mysql_real_connect() 传递 flags 参数 CLIENT_MULTI_STATEMENTS 等同于传递参数 CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS。也就是说,CLIENT_MULTI_STATEMENTS 足以启用多语句执行和所有多结果处理。

  • 在建立到服务器的连接后,可以使用 mysql_set_server_option() 函数通过向其传递参数 MYSQL_OPTION_MULTI_STATEMENTS_ONMYSQL_OPTION_MULTI_STATEMENTS_OFF 来启用或禁用多语句执行。使用此函数启用多语句执行也会启用处理多语句字符串的简单结果,其中每个语句产生单个结果,但这不足以允许处理产生结果集的存储过程。

以下过程概述了处理多个语句的建议策略

  1. mysql_real_connect() 传递 CLIENT_MULTI_STATEMENTS,以完全启用多语句执行和多结果处理。

  2. 在调用 mysql_real_query()mysql_query() 并验证其成功后,进入一个循环,您将在其中处理语句结果。

  3. 对于循环的每次迭代,处理当前语句结果,检索结果集或受影响的行计数。如果发生错误,退出循环。

  4. 在循环结束时,调用 mysql_next_result() 检查是否还有其他结果存在,如果存在,则启动对它的检索。如果不再有结果,则退出循环。

以下显示了上述策略的一种可能的实现。循环的最后部分可以简化为简单测试 mysql_next_result() 是否返回非零值。代码按原样区分没有更多结果和错误,这使得能够针对后者情况打印消息。

/* connect to server with the CLIENT_MULTI_STATEMENTS option */
if (mysql_real_connect (mysql, host_name, user_name, password,
    db_name, port_num, socket_name, CLIENT_MULTI_STATEMENTS) == NULL)
{
  printf("mysql_real_connect() failed\n");
  mysql_close(mysql);
  exit(1);
}

/* execute multiple statements */
status = mysql_query(mysql,
                     "DROP TABLE IF EXISTS test_table;\
                      CREATE TABLE test_table(id INT);\
                      INSERT INTO test_table VALUES(10);\
                      UPDATE test_table SET id=20 WHERE id=10;\
                      SELECT * FROM test_table;\
                      DROP TABLE test_table");
if (status)
{
  printf("Could not execute statement(s)");
  mysql_close(mysql);
  exit(0);
}

/* process each statement result */
do {
  /* did current statement return data? */
  result = mysql_store_result(mysql);
  if (result)
  {
    /* yes; process rows and free the result set */
    process_result_set(mysql, result);
    mysql_free_result(result);
  }
  else          /* no result set or error */
  {
    if (mysql_field_count(mysql) == 0)
    {
      printf("%lld rows affected\n",
            mysql_affected_rows(mysql));
    }
    else  /* some error occurred */
    {
      printf("Could not retrieve result set\n");
      break;
    }
  }
  /* more results? -1 = no, >0 = error, 0 = yes (keep looping) */
  if ((status = mysql_next_result(mysql)) > 0)
    printf("Could not execute statement\n");
} while (status == 0);

mysql_close(mysql);