쿼리 파라메터 방식

Type A

QueryEx2("ip_TMSget_touraccount_v2 %d,%d,%d,%d",dwUserNo,dwTourLogNo,bTourType,tableNo); 

Type B

CXString szQuery, szParam;
 szQuery.Format( "dbo.ip_TMSget_touraccount_v2 " );
 szParam.Format( "I=%d&,& I=%d&,& I=%d&,& I=%d", 
  lpUd->dwUserNo,
  lpTmd->dwTourLogNo,
  lpTmd->bTourType,
  pTable->GetTableNo() );


위 두 SP를 호출하는두 쿼리는 동일하며,  RDBMS인 MSSQL에 전송가능한 명령이였다.(현재는 되는지 모름)

좀더 유져 친화적인 Type A방식을 사용할수 있음에도 불구하고 이것이 10년전 성능의 이유로 사용이 금기시 되었다라고하면

믿기 어려울 것이다. - 마치 ORM이 금기시 되는것처럼


스케일 아웃이 어려웠고 시스템 성능도 아주 제약적이라 스케일 업으로 극단적인 튜닝을 했던 시절로

다음과 같은 이유가 있었다.


우선 DBMS에 쿼리 문을 전송하면 다음과 같은 과정을 통해 명령어를 실행한다.(더 복잡하지만 간략화하였다.)


I=%d 와 같이 TYPE이 분석된 전달값을 어플리케이션 레벨의 쿼리로 사용하였으며 어플리케이션이 이미 Type분석된

값을 전달함으로 DBMS가 해야하는 전달값 Type분석이라는 과정에 소요되는 비용을 어플리케이션이 분담할수 있었다.

쿼리가 동일하고 DBMS의 실행계획에 큰 차이가 없다고 하면 10만건 이상 호출하였을시 실제 DBMS의

CPU 를 절약할수 있는 방법이였다.  ( 현재의 시스템은, 이 차이는 미비할것으로 생각되며 라이브러리가 해줄수도 있을것으로 본다.)


S=%d&,& I=%d&,&S=%d&,& I=%d

위 코드의 단점은 전달값이 복잡해질시, 해시처럼 암호코드로 보인다란점이다.

쿼리 에러분석시 상당히 애를 먹었던 것같다. 전달 Type과 값이 일치하지 않는다란 런타임 오류에 대응하여 잘못된 코드를 찾기가 힘들다.

위와같은 방식이 아니면 DBMS의 CPU를 0.0001 더 먹기때문에, 과거 성능을 위해 호출비용을 극단적으로 줄여야했다.


최근은  개발 친화적인 ORM,GraphQL등 사용자 친화적인 요소를 중요하게 생각한다.

게임분야에서도 부분적으로 어셈블리어가 필요했던 시기가 있었지만,극단적 튜닝보다 더 중요한 다른것에 집중하는것이 좋다.

아래 코드는 DBMS 쿼리분석에대한 CPU시간을 줄이기위해 실제 사용하고 유지했던 코드이다.

BOOL CDBTool::Query(LPCTSTR szQuery, ...)
{
	if(!szQuery) return FALSE;

	CheckReconnect();   // DB 연결이 끊어졌으면 다시 연결 시킨다

	register int	n;
	va_list	        argptr;

	if(!m_szQuery)
	{
		m_szQuery=(LPSTR)calloc(1, MAX_QUERY_ADD_LENGTH);
		if(!m_szQuery) return FALSE;
		m_nQueryLength=MAX_QUERY_ADD_LENGTH;
	}

	va_start(argptr, szQuery);

	while((n=_vsnprintf(m_szQuery, m_nQueryLength-1, szQuery, argptr))<0)
	{
		m_nQueryLength+=MAX_QUERY_ADD_LENGTH;
		m_szQuery=(LPSTR)realloc(m_szQuery, m_nQueryLength);
		if(!m_szQuery)
		{
			va_end(argptr);
			m_szQuery=NULL;
			m_nQueryLength=0;
			return FALSE;
		}
	}

	va_end(argptr);

	m_szQuery[n]=0;

	if(m_hStmt!=NULL)
	{
		SQLFreeHandle(SQL_HANDLE_STMT,m_hStmt);
		//SQLFreeStmt(m_hStmt, SQL_DROP);
		m_nErrorNo=SQLAllocHandle(SQL_HANDLE_STMT,m_hDBC,&m_hStmt);
		//SQLAllocStmt(m_hDBC, &m_hStmt);
	}
	else
	{ 
		m_nErrorNo=SQLAllocHandle(SQL_HANDLE_STMT,m_hDBC,&m_hStmt);
		//m_nErrorNo=SQLAllocStmt(m_hDBC, &m_hStmt);
	}

	// 일정한 시간이 지날때까지 쿼리가 풀리지 않으면 해당 쿼리를 무시하고 루틴을 빠져나가게 셋팅한다.
	SQLSetStmtAttr(m_hStmt,SQL_ATTR_QUERY_TIMEOUT,(void*)m_LongQueryWaitTime,SQL_IS_UINTEGER);

	time_t start_time,elsp_time;   // 시작 시간과 지난시간을 가져온다.

	time(&start_time);

	m_nErrorNo=SQLExecDirect(m_hStmt, (SQLCHAR*)m_szQuery, SQL_NTS);

	time(&elsp_time);

	if ( (elsp_time - start_time) >= m_LongQueryLogTime )    // 쿼리가 지정한 시간 이상으로 오래 걸리면
	{
#ifdef _DEBUG
		MessageBox(NULL,"timeover warnning.","SQL TIME",MB_OK);
#endif

		FILE * f;
		char datebuf[20],timebuf[20] ;

		_strdate(datebuf);
		_strtime(timebuf);

		f = fopen("query_timeover.log","a");
		fprintf(f,"%s %s %d | %s \n",datebuf,timebuf,elsp_time - start_time,m_szQuery);
		fclose(f);
	}

	if(m_nErrorNo!=SQL_SUCCESS && m_nErrorNo!=SQL_SUCCESS_WITH_INFO) 
	{
		FILE * f;
		static char btz=0;
		char datebuf[20],timebuf[20] ;

		if (!btz)
		{
			_tzset();
			btz = 1;
		}

		_strdate(datebuf);
		_strtime(timebuf);

		_TUCHAR* szErrorMsg;
		_TUCHAR* pSqlState;
		SDWORD	NativeError;
		SWORD	svErrorMsg;

		szErrorMsg = new _TUCHAR[ SQL_MAX_MESSAGE_LENGTH-1];
		pSqlState = new _TUCHAR[SQL_MAX_MESSAGE_LENGTH-1];

		SQLError(m_hEnv, m_hDBC, m_hStmt, pSqlState, &NativeError, szErrorMsg, SQL_MAX_MESSAGE_LENGTH-1, &svErrorMsg);

#ifdef _DEBUG
		MessageBox(NULL,(char*)szErrorMsg,"SQL ERROR",MB_OK);
#endif

		f = fopen(m_szFileName,"a");
		fprintf(f,"%s %s %s %s \n",datebuf,timebuf,pSqlState,m_szQuery);
		fclose(f);

		// Connection does not exist.
		if (memcmp(pSqlState,"08003",5)==0)
		{
			m_isConn = FALSE;
		}
		// Communication link failure. The communication link between the driver and the data source 
		// to which the driver was connected failed before the function completed processing. 
		else if (memcmp(pSqlState,"08S01",5)==0)
		{
			m_isConn = FALSE;
		}

		// SQL 상태코드들이 들어 있습니다.
		delete pSqlState;
		delete szErrorMsg;
		return FALSE;
	}

	return	TRUE;
}


저수준의 IO를 제어하는 객체를 개발팀에서 직접 제작하는 시대는 유료/오픈진영의 라이브러리의

발전과 함께 사라졌다.


최근은 다음과 같은 것이 이슈가 되고 있다.

  • SP또는 쿼리 직접 호출하여 개발하는 방식
  • 쿼리를 맵퍼와 함께 관리하는 방식 ( SQL MAPPER : mybatis,dapper)
  • 관계도 까지 맵핑하는 방식 ( ORM : JPA,ENTITY )
  • CRUD API까지 뽑아주는 방식 : GraphQL


SQL을 직접 작성하지 않는 방식들이 등장하고 있지만

빅데이터 영역에서 NOSQL은 SQL을 쓰지않겠다는 의미가 아니고, SQL문만을 쓰지 않겠다란 의미다.

이말은 SQL부터 깊이있게 더 공부하고 , 하나더 공부해야함 의미 한다.

SQL공부없이 ORM부터 접근하는 젊은 개발자들에게 들려주고 싶은 이야기이기도 하다. 



  • No labels