AND vs. OR(IN)

  • 엑세스 처리방법(OR는 분리), 처리범위(OR는 증가)에 있어 AND와 OR는 매우 큰 차이를 가진다.
  • 이 장에서는 OR(IN)연산자의 정확한 활용방법뿐만 아니라 이를 극복하여 오히려 최대의 효율을 얻도록 하는 다양한 방법들을 제시한다.
  • OR나 IN은 개념적으로는 거의 동일한 연산자이다.

I. OR와 IN의 비교

  • OR: 어떠한 논리합 관계도 표현 가능
  • IN: 하나의 컬럼이 여러개의 '=' 조건을 가지는 경우에만 가능
  • OR는 IN을 포함한다.(고로 IN을 사용해 표현할 수 있는 것은 당연히 OR로 표현할 수 있다.)
    1) IN \-> OR(가능)
INOR
SELECT *

FROM TAB1

WHERE COL IN ( '1','5','7')

AND COL2 = '111';
SELECT *

FROM TAB1

WHERE (COL1 ='1' OR COL1 ='5' OR COL1 ='7')

AND COL2 = '111';

2) OR \-> IN(불가능)

OR
SELECT * FROM TAB1

WHERE ( COL1 ='1' OR COL2 = '500' OR COL2LIKE '7%' )

AND COL2 = '111';
  • OR 보다는 IN을 사용하자.
    1) IN은 서비쿼리를 사용하여 보다 유연한 확장이 가능하다.
    2) IN은 반드시 하나의 컬럼이 비교되어야 하므로 나중에 인덱스 구성에 대한 전략을 수립할 때 유리하다.
    3) 옵티마이저는 복잡한 OR에 매우 취약하다.

II. OR와 AND의 비교

  • OR: 많이 사용할수록 집합의 범위를 크게 만들어 일량이 증가한다.
  • AND: 많이 사용할수록 집합의 범위를 줄여주어 수행속도가 향상된다.
  • 하지만 이것은 연산자가 처리주관 조건에 사용되었을 때에 한해 적용된다.
  • 처리 주관 조건은 인덱스 구조에 따라 언제든지 달라질 수 있으므로 가능한 OR 조건은 사용하지 않도록 한다.

처리주관 조건에 확실히 속하지 않는다면 OR 연산자는 오히려 수행속도에 도움을 준다.
(처리주관 범위는 '좁을수록' 유리하고 체크조건은 '넓을수록' 유리하다.)

Ⅲ. 논리합 연산자의 실행계획

1. OR의 실행계획


SELECT *
FROM TAB1
WHERE A LIKE 'S%' OR B = '1';


SELECT STATEMENT Optimizer=ALL_ROWS (Cost=2 Card=2 Bytes=520)
      CONCATENATION
        TABLE ACCESS (BY INDEX ROWID) OF 'TAB1' (TABLE) (Cost=1 Card=1 Bytes=260)
          INDEX (RANGE SCAN) OF 'TAB1_B' (INDEX) (Cost=1 Card=1)
        TABLE ACCESS (BY INDEX ROWID) OF 'TAB1' (TABLE) (Cost=1 Card=1 Bytes=260)
          INDEX (RANGE SCAN) OF 'TAB1_A' (INDEX) (Cost=2 Card=1)

  1. 논리합 연산자를 처리주관 조건으로 할 때는 각각 별도의 최적화 실행계획으로 분리한 후 이들을 결합(Concatenation)하는 실행계획을 수립한다.
  2. 논리합 연산자가 사용된 처리주관 조건은 분리된 각 조건의 처리방법이 어느 한가지 방법의 진부분 집합이 되면 그 처리방법에 귀속된다.
  3. 서로 다른 인덱스를 액세스하지만 마치 하나의 인덱스를 경유할 때와 큰 차이가 없다.
    (on) 정상적으로 실행계획의 분기가 일어난다면 OR 연산자도 거의 문제점을 가지지 않는다.
  4. 각각의 처리된 집합을 결합하기 위해서 별도의 머치, 정렬 작업이 필요하지 않으므로 부분범위처리가 아직도 유효하다.
  5. 분기된 처리범위 간에 공통부분(교집합)이 많이 존재하면 불필요한 액세스가 일어난다.
    (on) 정상적으로 실행계획이 수립된 경우라도 OR 연산자의 사용이 불리한 경우이며 역으로 이와 같은 문제가 발생하지 않는다면 전혀 OR 연산자는 부담이 없음을 의미한다.
  6. 실행계획 분리시에는 WHERE 절에서 나중에 기술된 조건이 먼저 수행된다.(오라클에서 나타나는 현상이며 다른 제품은 확인 필요)
    추출될 집합을 정렬하지 않고서도 이들의 순소를 조절할 수 있음을 의미한다.(부분범위처리의 한가지 활용방법)

2. IN의 실행계획


SELECT *
FROM TAB1
WHERE A IN ('5', '1');


SELECT STATEMENT
      CONCATENATION
        TABLE ACCESS (BY INDEX ROWID) OF 'TAB1'
          INDEX (RANGE SCAN) OF 'TAB1_A' (NON-UNIQUE)
        TABLE ACCESS (BY INDEX ROWID) OF 'TAB1'
          INDEX (RANGE SCAN) OF 'TAB1_A' (NON-UNIQUE)

  • 위의 OR를 사용한 경우와 거의 동일한 특징을 가지며 굳이 차이를 말한다면 분기된 처리범위 간에 공통부분이 전혀 없으므로 불필요한 액세스는 전혀 발생하지 않는다는 것이다.

IV. OR 연산자 사용시의 주의사항

OR 연산자 사용시 유의 사항

  • 데이터 모델링 단계에서 속성 결정시 OR 연산자 사용 가능성을 고려하라
  • OR 의 적용범위를 가급적 축소하라
  • 복잡한 OR는 옵티마이져에게 혼선을 유발시켜 의도치 않은 실행 계획을 만들수 있으므로 간단한 OR를 사용하라
  • 상수쪽을 DECODE 등으로 가공하여 OR 연산자를 없애도록 노력하라
  • 필터링(Filtering)을 통하여 분리 실행계획을 만들고자 할 경우에는 UNION ALL 을 사용하려 SQL을 분리하라

1. 실행계획 분할 방법

  • 입력된 조건값에 따라 처리주관 인덱스의 사용을 분리하고 싶을 때 OR 연산자를 이용하지 말고 UNION ALL{}을 이용한다.

  • 어느 회사에서 '거래처별 매출현황'을 조회하고자 한다.
    거래처의 평균 분포도는 1% 이하이지만 주거래 대상 업체인 대한물산(거래처코드:101)은 전체의 60%가 넘는다.
    인덱스는 '거래처코드+매출일자'로 생성되어 있다.
    '대한물산'의 자료를 검색할 때는 넓은 범위의 인덱스 스캔으로 인한 랜덤 액세스를 줄이기 위해 '전체 테이블 스캔' 실행계획을 수립하고 그렇지 않은 경우에는 위의 인덱스를 사용하는 실행계획을 유도하려면?
    • OR 사용

    SELECT SUBSTR(매출일,1,6), SUM(매출액)
           SUM(손익액),
           SUM(손익액)/SUM(매출액)*100
    FROM 매출순익
    WHERE ( :IN_CUST <> '101' AND 거래처코드 = :IN_CUST AND 매출일 LIKE 'INDATE||%')
    OR    ( :IN_CUST ='101' AND RTRIM(거래처코드) = :IN_CUST AND 매출일 LIKE :IN_DATE||'%')

바인딩 변수(:IN_CUST)가 사용된 SQL문장을 파싱할 때는 변수인 상태로 실행계획을 수립하고 실행되는 순간 입력된 상수값을 바인딩하여 실행한다.
그러므로 옵티마이저는 어떤 입력값이 들어오든 하나의 실행계획, 즉 '전체 테이블 스캔'이 된다.(인덱스 사용은 전체 테이블 스캔의 진부분집합이다.)

    • UNION ALL 사용

    SELECT SUBSTR(매출일,1,6), SUM(매출액),
           SUM(손익액),
           SUM(손익액)/SUM(매출액)*100
    FROM 매출순익
    WHERE :IN_CUST <> '101'
    AND 거래처보드 = :IN_CUST
    AND 매출일 LIKE IN_DATE||'%'
    UNION ALL
    SELECT SUBSTR(매출일,1,6), SUM(매출액),
           SUM(손익액),
           SUM(손익액)/SUM(매출액)*100
    FROM 매출순익
    WHERE :IN_CUST = '101'
    AND RTRIM(거래처보드) = :IN_CUST
    AND 매출일 LIKE IN_DATE||'%'

2. OR 연산자의 해소


    SELECT CHULNO, CUSTNO, CHULDATE, UNCOST
    FROM CHULGOT
    WHERE ( :SW = 1 AND ( STATUS LIKE '1%' OR STATUS LIKE '2%') )
    OR    ( :SW = 2 AND ( STATUS LIKE '3%'))
    ORDER BY STATUS ;

  • 위와 같은 복잡한 OR은 STATUS 컬럼이 인덱스를 가지고 있더라도 '전체 테이블 스캔'으로 실행계획이 수립된다.
  • 사용된 조건을 보면 이 SQL의 처리범위는 좁지 않다.
  • ORDER BY가 사용되어 부분범위처리도 불가능하다.
    => 만약 테이블이 매우 많은 데이터를 가지고 온라인 화면에서 사용된다면 심각한 문제를 초래한다.

    SELECT CHULNO, CUSTNO, CHULDATE, UNCOST
    FROM CHULGOT
    WHERE STATUS LIKE DECODE(:SW,1,'2%')
    OR    STATUS LIKE DECODE(:SW,1,'1%','3%') ;


CONCATENATION
   TABLE ACCESS BY ROWID CHULGOT
     INDEX (RANGE SCAN) STATUS_INDEX
   TABLE ACCESS BY ROWID CHULGOT
     INDEX (RANGE SCAN) STATUS_INDEX

  • 우리가 원하는 결합처리 방법으로 실행계획이 수립된다.
  • STATUS_INDEX를 타게 되므로 추출되는 로우는 자연적으로 STATUS로 정렬되어 나타나므로 ORDER BY는 필요없다.
  • DECODE 처리 방법
    • :SW=1
      WHERE STATUS LIKE '2%' OR STATUS LIKE '1%';
    • :SW=2
      WHERE STATUS LIKE NULL OR STATUS LIKE '3%';
  • STATUS 컬럼에 NULL값이 많이 포함되어 있어도 NULL은 'IS NULL'일 경우에만 값을 찾기 때문에 위의 STATUS LIKE NULL 조건을 만족하는 값은 언제나 존재하지 않는다.

3. 데이터 모델링 시의 유의사항


SLEECT ...
FROM HCMT_010 ..
WHERE ( 투입일시 LIKE :V_DATE||'%'
    OR  출고일시 LIKE :V_DATE||'%'
   OR  입문일시 LIKE :V_DATE||'%'
   OR  출문일시 LIKE :V_DATE||'%'
   OR ... )
AND 생산라인 = :V_LINE

  • 생산라인에 인덱스가 없고 OR 조건의 컬럼중 하나의 컬럼이라도 인덱스가 구성되어 있지 않다면 '전체 테이블 스캔'으로 처리된다.
  • 생산라인에 독자적으로 인덱스가 생성되어 있다면 OR 조건들은 처리주관 자격이 없으므로 '생산라인' 인덱스만 처리주관 인덱스가 된다.
  • 일자에 관련된 컬럼이 위와 같이 4개로 나뉘어져 있을 경우 효율적인 처리를 위해서는 '생산라인'과 모든 '일시'를 결합하는 인덱스를 생성해 주어야만 한다.

SLEECT ...
FROM HCMT_010 ㅌ, ADDED_TABLE y
WHERE x.차대번호 = y.차대번호
   and  x.발생일시 LIKE :V_DATE||'%'
   and y.생산라인 = :v_line;
AND 생산라인 = :V_LINE

  • 투입일시, 출고일시, 입문일시, 출문일시를 모두 '발생일시'라는 컬럼으로 정의한다.
  • 발생일시는 하나 이상의 값을 가지므로 제 1정규화에 의해 별도의 테이블로 분리된다.(ADDED_TABLE)
  • 일시를 포함하는 무수한 많은 결합 인덱스 대신 발생일시를 포함하는 인덱스만 가지면 되며 이로인해 인덱스 개수도 현격하게 감소되고 SQL도 보다 간결해진다.

=> 데이터 모델링 단계에에 실시하는 컬럼 하나의 정의도 미래에 발생할 활용도와 액세스 효율을 고려하여 선정하여야 할 것이다.

문서에 대하여