SQL 2015. 5. 20. 13:04

Loop 쿼리를 제거하고 One 쿼리를 작성해야 하는 이유

링크 : 

  1. 1. 데이터 파일의 구조
  2. 2. DBMS 메모리 구조
  3. 3. 인덱스 기본
  4. 4. 정렬을 제거하는 방법
  5. 5. 프로시져 캐시 경합 확인하기
  6. 6. Loop 쿼리 제거하기
  7. 7. 튜닝 결과 샘플 보기




데이터베이스 작업 요청에 따른 Call 의 종류를 살펴보면

  • Parse Call : SQL 파싱을 요청하는 Call
  • Execute Call : SQL 실행을 요청하는 Call
  • Fetch Call : SELECT 문의 결과 데이터 전송을 요청하는 Call

일반적으로 서버에서 DB에 쿼리를 요청하는 코드는 


WorkThread::Func()

{

……

…..

Lib.api_call(_T(“실행 쿼리”));

Blocking...

}


응답이 돌아올 때까지 해당 쓰레드는 Blocking 상태로 다음 작업을 할 수 없다.


만약 바인딩 변수로 대상 아이템의 시퀀스를 받아 해당 아이템 정보를 얻는 쿼리가 있다고 가정하고 이 쿼리를 이용하여 아이템 30개의 정보를 얻는 서버 로직을 생각해보자.


일반적으로 아래와 같은 형식으로 작업 할 것이다.


WorkThread::SelectFunc()

{

    ...

    

    // Loop Call

    for (int i = 0; i < 30; ++i)

    {

        Lib.api_call(_T("select 쿼리"));

        Blocking...

    }

}


이런 형태의 코드는 DBMS 내부적으로 Parse Call, Execute Call 이 각각 30회씩 발생하고, 추가로 30회의 응답 대기가 발생한다.


Loop 쿼리는 대부분의 시간을 네트워크 구간에서 소비하거나 Call 이 발생할 때마다 OS로부터 CPU와 메모리 리소스를 할당 받으려고 대기하면서 소비한다.


만약 One 쿼리를 만들어 실행 시킨다면


WorkThread::SelectFunc()

{

    ...


// One Call

Lib.api_call(_T(“select 쿼리”));

      Blocking...    

}


Parser Call, Execute Call 이 각각 1회씩만 발생된다. 추가로 가장 큰 이득은 Blocking 상태 (네트워크 구간 + 리소스 할당 대기 + 쿼리 처리)가 1회로 끝나는 것이다.


One SQL은  대상 그룹을 문자열로 넘겨 받아 파싱 후 테이블 변수로 만들어 조인한다.


만약 테이블 변수와 조인을 하게 되는 대상 테이블이 동일한 페이지에 정렬되어 Page 접근이 적어진다면[각주:1] 조인 효율은 극대화 될 것이다.


One Call 쿼리에 30개의 대상 아이템 시퀀스를 문자열로 넘긴다면 대략  "1, 2, ..., 30"  이런 형식이 될 테고


해당 문자열을 파싱해서 테이블 변수로 만드는 쿼리는 


@strArray 는 바인딩 변수


SET NOCOUNT ON;

-- 테이블 변수 가능한 작게 유지할 필요가 있다.

DECLARE @tTemp TABLE (

Seq BIGINT

);

DECLARE @Seq BIGINT = 0;

    WHILE CHARINDEX(',', @strArray) <>0

BEGIN

SET @Seq = CAST(SUBSTRING(@strArray, 0, CHARINDEX(',', @strArray)) AS BIGINT)

SET @strArray = SUBSTRING(@strArray, CHARINDEX(',', @strArray) + 1, LEN(@strArray) - CHARINDEX(',', @strArray))

INSERT INTO @tTemp

SELECT @Seq

END

    

    IF 0 < LEN(@strArray)

BEGIN

SET @Seq = 0

SET @Seq = CAST(@strArray AS BIGINT)

INSERT INTO @tTemp

SELECT @Seq

END

SELECT 

...

FROM @tTemp AS O

INNER JOIN dbo.targetTable AS I

ON O.Seq = I.Seq

WHERE 튜닝 규칙을 만족하는 범위 한정 조건절 작성



대략 이런 형식이 될 것이다.


만약 테이블 변수를 사용하지 않고 파싱 중간중간 결과를 Select 한다면 TDS 패킷 수량이 많아진다. 


즉 DB에서 서버에 전달하는 패킷이 더 많아지기 때문에 불필요한 비용이 발생할 수도 있다. 


조인 대상 테이블 구조에 따라 랜덤 액세스 비용이 발생할 수도 있기 때문에 꼼꼼히 살펴볼 필요는 있지만 Blocking 상태에서 소모되는 시간보다는 훨씬 적을 것이다.


상대적인 IO 비용


 IO

비용 

L1 cache

3 cycles 

L2 cache

14 cycles 

RAM

250 cycles 

DISK 

41000000 cycles 

Network 

240000000 cycles 








  1. DB는 하나의 레코드에 접근하더라고 레코드가 포함된 Page를 메모리에 로드한 후 로드된 메모리 블럭을 통해 레코드에 접근한다. [본문으로]