Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
languagecpp
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년전 성능의 이유로 사용이 금기시 되었다라고하면

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

다음과 같은 이유에 의해서 였다.


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

draw.io Diagram
bordertrue
viewerToolbartrue
fitWindowfalse
diagramName쿼리변환
simpleViewerfalse
width
diagramWidth728
revision3


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

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

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

CPU 를 절약할수 있는 방법이였다.  ( 현재의 시스템은, 이 차이는 미비할것으로 생각되며 더 복잡한 요인이 존재)


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

위 코드의 단점은 전달값이 복잡해질시, 해시와같은 암호코드같이 된다란것이다. 쿼리 에러분석시 상당히 애를 먹었던 것같다.

Type A를 통해 TypeB에 해당하는 코드를 만들수 있었지만, 기존 코드보다  cpu를 0.001을 더 사용하는 방식은 어쨋건 허용되지 않았다.


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

최근은 DBMS를 이용하는 안정적이고 편리한것들이 많아 사용자 친화적인 쿼리가 바로 전송되는지

중립어로 한번 변환이 되는지 신경쓰지 않는 편이다. ( 신경쓰지 않고 더 중요한 다른것에 집중하는것이 좋다.)

Code Block
collapsetrue
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;
}


위 방식에대한 고민은 오래되어 이미 사라졌으며

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

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

요즘은 대용량 처리를 위해 RDBMS에게만 의존하지 않기 때문에...

팀 조직내에서 DB를 사용하는 개발코드가 ,유연하고 안정적이고 분산처리가 되는것에대해  어떤것이 더 이득이 되는가? 를 잘 따져 봐야할것이다.