본문 바로가기
Server | Network

Error: 2014 (CR_COMMANDS_OUT_OF_SYNC) with SELECT command

by 두루물 2011. 2. 15.


당연한 얘기지만 결과셋이 존재할지 모르는 SELECT 는 단순히 mysql_real_query() 혹은 mysql_query() 만 호출하고 끝나서는 안된다. 하지만,당연한 것을 당연하지 않게 구현된 상용 서버 어플리케이션의 주먹구구식의 허접소스의 경우가 있다니 놀라울 따름이다..,

1
2
3
4
5
6
7
8
9
10
if(mysql_query("SELECT ~~"))
{
   //No Data 처리 
   mysql_query("또다른 SELECT 1 ~~")
 
}
else{
  mysql_query("또다른 SELECT 2 ~~")

}

처럼 No Data 처리를 하고 있으니 말이다.

이럴 경우 No Data 를 판별하는 것도 틀렸지만 특히 다음 쿼리시 위 CR_COMMANDS_OUT_OF_SYNC "Commands out of sync; you can't run this command now" 에러가 연이어 발생할 수 있기 때문이다.
이 함수들을 단독으로만 호출할 경우는 데이터를 리턴하지 않는 SQL 문 UPDATE,INSERT,DELETE 명령에만 사용하여야 한다.

mysql_real_query() 혹은 mysql_query() 에 "SELECT ~" 쿼리를 주든 데이터를 리턴하지 않는 SQL문을 주든,이 리턴값은
데이타의 유무와는 무관한 쿼리자체의 성패여부에 대한 리턴값이다.
아주 간혹,이부분에 대해 위의 두가지 사항에 대해 잘못 이해하여 코딩된 소스코드들이 난무하는 것 같다.

아래의 소스코드는 MySQL API 함수를 C++ 클래스화 한 내용중 이 두부분에 대한 부분만 발췌 하였다.
Query는 데이타를 리턴하지 않는 SQL문에 사용되는 권장사항이고

QuerySelect 는 결과 셋이 존재하는 SELECT 문(결과값이 없더라도) 사용시 사용되어야 하는 함수이다.

그렇다고 이렇듯, 이런 클래스를 제공받은 다른 사용자(제2,3의 개발자)가 Query() 함수에 "SELECT" 문을 쓸수도 있으니
조금더 깔끔하게 예외처리를 넣어놓도록 하자.

res = mysql_use_result(&mysql);
를 호출하여,
결과셋이 있는 쿼리인 경우는 row = mysql_fetch_row(res);
한개만 하여 결과가 있는지 없는지 BOOL 타입의 true,false로 결과여부를 판단하도록 하고,
UPDATE,INSERT,DELETE 처럼 결과셋이 없는 함수는 res = NULL 이 될것이므로,
이때에는 true를 리턴하도록 하면 된다.

또는,이렇게 하지 않고 "SELECT 문은 이 함수로 사용하지 맙시다" 라는 안내문구를 내보이고 리턴하도록....


//데이터를 리턴하지 않는 SQL 문에 사용하기를 권장한다.
//권장함수:결과셋이 존재할지 모르는 SELECT 는 반드시 QuerySelect을 호출한다.
//http://www.ibm.com/developerworks/kr/library/l-sql.html
//use to INSERT,UPDATE,DELETE
//http://www.mysqlkorea.co.kr/sub.html?mcode=manual&scode=01&m_no=21927&cat1=22&cat2=596&cat3=606&lang=k

bool CMySQLDB::Query(char * szQuery,int length)
{
 bool bc = false;
 MYSQL_RES *res = NULL;
 if(mysql_real_query(&mysql,szQuery,length)){ //쿼리자체에 대한 성패여부이다.No Data와는 무관함
  int dmerr = mysql_errno(&mysql);
  const char *err = mysql_error(&mysql);
  printf("Query,err=%s,retry again\n",err);
  tracelog(0,"Query,err=%s,retry again\n",err);
  return false;
 }
 //쿼리 결과값이 있는데,mysql_use_result나 mysql_store_result 하지않으면,
 //다음 쿼리에서 아래 오류가 발생하거나 남아있던 결과셋이 다음 쿼리에 병합되어 버린다.
 //즉,그러니까 SELECT 문은 단순한 mysql_real_query만 호출하고 끝내면 안된다.
 //권장함수:결과셋이 존재할지 모르는 SELECT 는 반드시 mysql_free_result처리까지 완료하는
 //QuerySelect을 호출한다.
    //For prevent Error: 2014 (CR_COMMANDS_OUT_OF_SYNC) with SELECT command
    //Message: Commands out of sync; you can't run this command now 
    // >>>
 MYSQL_ROW row;
 res = mysql_use_result(&mysql);
 if(res){
  row = mysql_fetch_row(res);
  if(row)
   bc = true;
  mysql_free_result(res);
 }
 else //데이터를 리턴하지 않는 SQL 문
  bc = true;
 printf("Query OK,bc = %d res = %x,err=%s,retry again\n",bc,res,geterrmsg());
 // <<<
 return bc;
}

int CMySQLDB::QuerySelect(MYQUERYSELECT *qs,char * szQuery,int length)
{
 MYQUERYSELECT *tqs = (qs == NULL) ? &m_qs : qs;

 if(mysql_real_query(&mysql,szQuery,length))
 {
  const char *err = mysql_error(&mysql);
  tracelog(0,"err=%s,retry again\n",err);
  bool bc = IsConnected();
  if(bc == 0){
   tracelog(0,"connection lost!\n");
  }
  if(mysql_real_query(&mysql,szQuery,length)){
   tracelog(0,"err=%s,failed!!\n",err);
   return -1;
  }
 }
 if(tqs->res != NULL)
  mysql_free_result(tqs->res);
 tqs->res = mysql_store_result(&mysql);
 if(tqs->res == NULL)
 {
  tracelog(0,"mysql_store_result() failed:\nQuery: (%s)\nError %u (%s)\n",
   szQuery, mysql_errno(&mysql), mysql_error(&mysql));
  return -1;
 }

// field_count = mysql_num_fields(res);
// fields = mysql_fetch_fields(res);
 tqs->row_pos = 0;
 tqs->row_count = (int)mysql_num_rows(tqs->res);//do not use with mysql_use_result()
 tqs->row = mysql_fetch_row(tqs->res);
 return tqs->row_count >= 0 ? tqs->row_count : -1;
}
 



 *mysql_use_result() 은 mysql_store_result() 와는 다르게, 모든 결과셋을 클라이언트 메모리로 내려받지 않고 step by step 으로 한행씩 결과를 받아온다. 이것은 임시 테이블이나 로컬 버퍼에 저장하지 않고 서버로부터 직접 쿼리 결과를 읽어서 다소 빠르고, mysql_store_result()함수보다 메모리를 적게 사용한다.즉, 결과셋이 대량 데이타셋여서 mysql_store_result()로는 메모리 한계가 있거나 데이타가 몇개 없고 처리속도를 빠르게 할 경우 유용하다.

단,한번에 클라이언트 메모리로 모든 결과셋을 가져오는 mysql_store_result() 와는 달리 mysql_data_seek(), mysql_row_seek(),mysql_row_tell(), mysql_num_rows(), 또는 ysql_affected_rows() 함수를 사용할 수 없으며 다른 쓰레드(작업권) 에서 해당 테이블에 변경을 시도할때 이 작업이 소요되는 동안 lock()이 되며,변경된 내용을 서로 실시간으로 반영되는 경우(매번 SELECT 가 되므로)
에 유리하다고 볼수 있다.

[MySQL Korea 저작권 공지 : http://www.mysqlkorea.co.kr/sub.html?mcode=others&scode=04