MySQL API函数(c/c++)操作 “不常见错误”详解

你有没有试过在执行函数中执行存储函数过后立刻执行其他sql语句?


今天开发的时候遇到一个数据库操作error 2014的问题,后面不断找文档、看connector源码才解决和明白之。
分享一下让后面遇到这个问题的人少走弯路。首先看一下问题的源码,本文只贴出c (采用Connector c)

和c++(采用connector c++)源码,其他语言道理相通。


数据库存储过程code:

CREATE DEFINER = `root`@`localhost` PROCEDURE `sp_test`()
BEGIN
select 1 as name;
END;

c code:

#include 
#include "mysql.h"  
int main(){
  const char *host = "127.0.0.1";
  const char *user = "uername";
  const char *pass = "123456";
  const char *db   = "dbname";
 /* 定义mysql变量 */
  MYSQL mysql;
  MYSQL_RES *rs;
  MYSQL_ROW row;

  if (mysql_init(&mysql) == 0) return -1;
	
	/* 连接数据库 */
	if (!mysql_real_connect(&mysql, host, user, pass, db,0 , NULL, 0))
  { 
		return -1;
  }

  if (mysql_query(&mysql, "call sp_test()") == 0 &&
	  mysql_field_count(&mysql) > 0 ) 
  { 
	 rs = mysql_store_result(&mysql);
	 mysql_free_result(rs); 				         
  }
  else
  {
	  printf("操作1失败\n");
  }

  if (mysql_query(&mysql, "select 1 as name") == 0 && //这里会操作失败
	  mysql_field_count(&mysql) > 0 ) 
  { 
	 rs = mysql_store_result(&mysql);
	 mysql_free_result(rs); 				         
  }
  else
  {
	  printf("操作2失败\n");
  }

   mysql_close(&mysql);
   return 0;
}

c++ code:

#include "mysql_connection.h"
#include "mysql_driver.h"
#include "cppconn/driver.h"
#include "cppconn/statement.h"
#include "cppconn/resultset.h"
#include "cppconn/exception.h"
using namespace sql;

#define DBHOST "tcp://127.0.0.1:3306"
#define USER "root"
#define PASSWORD "123456"

int main() {

	Driver *driver;
	Connection *con;
	Statement  *stmt;
	ResultSet  *res;
	Connection *conn;
	bool bResult;
	driver = get_driver_instance();
	conn = driver->connect(DBHOST, USER, PASSWORD);
	stmt = conn->createStatement();
	try
	{
		stmt->execute("use db_name; ");

		if (stmt->execute("call sp_test(); "))
		{
			res = stmt->getResultSet();
			if(res != NULL)
			{
			delete res;
			}
		}

		stmt->execute("select 3 as address;") //注意,这里会抛出异常2014 Commands out of sync
		{
			res = stmt->getResultSet();
			if(res != NULL)
			{
			delete res;
			}
		}
	}
	catch (SQLException& e)
	{
		//会捕获到异常
	}

	
	delete conn;
	delete res;
	delete stmt;
	driver = NULL;
	conn = NULL;
	return 0;
}


        可以发现,当调用了一个存储过程之后(有一个select返回),后面执行mysql_query都会失败,c++版本还会抛出异常(error 2014 CR_COMMANDS_OUT_OF_SYNC) mysql官方对此错误的定义:
http://dev.mysql.com/doc/refman/4.1/en/mysql-query.html

CR_COMMANDS_OUT_OF_SYNC : Commands were executed in an improper order.

命令以不合理的顺序执行,意味着前面还有未执行的命令?


其实平时一般的执行语句无论执行多少次都没问题,为什么执行带select的存储过程就会出问题?

继续查阅文档,发现这里有说明

http://dev.mysql.com/doc/refman/5.0/en/mysql-next-result.html

摘要 If your program uses CALL statements to execute stored procedures, 
the CLIENT_MULTI_RESULTS flag must be enabled. This is because each 
CALL returns a result to indicate the call status, 
in addition to any result sets that might be returned by statements executed within the procedure. 

Because CALL can return multiple results, process them using a loop that calls mysql_next_result() 

to determine whether there are more results.


就是说call执行存储过程后除了存储过程内的select会返回结果集外,另外还会返回一个

结果集用于标识存储过程执行的结果状态。刚才的"call sp_test"其实会返回两个
结果集,而我只取了第一个结果集,第二个结果集还在处理状态中,这时候执行下一条
语句就会报错。因此对于多结果集的执行语句应该这样写

c code:
 if (mysql_query(&mysql, "call sp_test()") == 0) 
  { 
	 do
	 {
		 if (mysql_field_count(&mysql) > 0)
		 {
			 rs = mysql_store_result(&mysql);
			mysql_free_result(rs);
		 }
	 }
	 while (mysql_next_result(&mysql) == 0);			         
  }


 c++ code:
 if (stmt->execute("call sp_test(); "))
 {
	do
	{
		res = stmt->getResultSet();
		if(res != NULL)
		{
			delete res;
			res = NULL;
		}
	}
	while (stmt->getMoreResults());
 }



注意:
第一次循环 mysql_next_result会返回0 (说明有结果集,状态结果集),
但是 stmt->getMoreResults() 却返回false(没有更多结果集?) ,那 c和c++不就是自相矛盾啦? 贴上Connector c++源码:
bool
MySQL_Statement::getMoreResults()
{
	CPP_ENTER("MySQL_Statement::getMaxRows");
	CPP_INFO_FMT("this=%p", this);
	checkClosed();
	last_update_count = UL64(~0);
	if (proxy->more_results()) {


		int next_result = proxy->next_result();


		if (next_result > 0) {
			//....
		} else if (next_result == 0) {
			return proxy->field_count() != 0; //这里是重点
		} else if (next_result == -1) {
			/.....
		}
	}
	return false;
};


        原来connnecotr c++的getMoreResults也是执行mysql_next_result(),只不过会自动过滤掉没有列的结果集,所以刚才上面对于存储过程返回的第二个结果集就返回false了。这里我也冒出了几个问题

1、不带select 的 存储过程执行是否也需要mysql_next_result
2、mysql_store_result 是否一定需要??

3、mysql_next_result 执行需要的条件?


与其不断乱猜,不如多看看源码,我将这几个函数实现的源码看了 一遍,整理出相关的流程图:


MySQL API函数(c/c++)操作 “不常见错误”详解_第1张图片



我们通过流程图可以看出,三个主要的api方法都严重依赖mysql->status, 和mysql->server_status
去确定是否可以执行,这两个变量也为mysql的命令按顺序执行,按顺序获取结果集提供了保证。
流程图看起来蛮费力,我简单地整理成如下的表格:




表格中可以清晰地看出在执行后各种返回状态的基础上各个函数的执行情况,现在回答刚才的几个问题:


1、不带select 的 存储过程执行是否也需要mysql_next_result

答:不带select的存储过程只有一个无filed的状态结果集,因此 
    mysql->status   = MYSQL_STATUS_READY
  mysql->server_status = 非SERVER_MORE_RESULTS_EXISTS
  符合序号3,因此可以直接执行下一个查询



2、mysql_store_result 是否一定需要??


答:序号 1 和 2要想迁移到序号3和4一定需要执行mysql_store_result,
   就是说返回的当前结果集当中若存在field字段,必须要执行mysql_store_result
   保存结构集才能执行其他的操作(只能获取一次)。

例子:
   mysql_query(&mysql, "select 1 as name; ") ; //执行后状态是序号1
   /*屏蔽以下这句会不会有问题?看上面状态图 */
   //rs = mysql_store_result(&mysql) 
   mysql_query(&mysql, "select 2 as age; ") ;



3、mysql_next_result 执行需要的条件?


答:只有序号3和序号4正常,因此
  mysql->status = MYSQL_STATUS_READY(无field结果集或者已执行mysql_store_result)
  是mysql_next_result正常执行的条件。



例子:
mysql_query(&mysql, "select 1 as name; ") ; //执行后状态是序号1
mysql_next_result(&mysql) ; //这句话会有问题??是的,看状态图



--------------------------------------华丽的分割线-------------------------------------------------


其实之前一直没注意那么多细节,因为不断执行就可以了,一个小问题带出那么多问题,

告诉我们问题无处不在,平时要细心发现。


转载请标示文章出处:http://blog.csdn.net/luoti784600/article/details/21532279










你可能感兴趣的:(C/C++)