- 논리합 연산자의 액세스 효율화
- 1. 논리합(OR,IN)연산자의 이해
- 1.1 OR 와 IN 의 비교
- 1.2 AND 와 OR 의 비교
- 1.3 논리합 연산자의 실행계획
- 1.4 OR 연산자 사용시의 주의사항
논리합 연산자의 액세스 효율화
- 데이터의 액세스 효율에 가장 큰 영향을 미치는 것은 역시 인덱스의 효율적인 활용에 있다.
- 관계형 데이터베이스는 옵티마이져에 의해 생성되는 실행계획에 따라 처리 범위가 좌우된다.
- IN 을 단지 하나의 연산자로만 생각하지 말고 여러 개의 '='이라는 생각으로 접근한다.
- 결합인덱스에서 자기보다 앞선 칼럼이 '='조건으로 변하는 순간 액세스할 범위는 현격 하게 줄어들 수 있다.
- LIKE, BETWEEN - 연속선 VS IN('=') 연속점
- '연속선'이 '연속점' 으로 변하는 순간 액세스 효율을 꾀할 수 있다.
1. 논리합(OR,IN)연산자의 이해
- 적용상의 차이는 약간 있겠지만 OR 나 OR 는 개념적으로는 거의 동일한 연산자이다.
1.1 OR 와 IN 의 비교
- OR : 논리합 관계 표현
- IN : 하나의 컬럼이 여러 개의 점, 즉 '=' 조건을 가지고 있는 경우에만 사용할 수 있다.
- IN을 사용해 표현할 수 있는 것은 당연히 OR로 표현할 수가 있다.
IN | OR |
---|
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' |
다음과 같이 비교할 칼럼이 다르거나, 연산자가 '='이 아닌 경우에는 IN으로 대체시킬 수 없다.
OR 로 대체 불가
|
---|
SELECT * FROM TAB1
WHERE ( COL1 ='1' OR COL2 = '500' OR COL2LIKE '7%' )
AND COL2 = '111';
|
IN을 사용할 수 있는 경우라면 OR보다 IN을 사용하는 것이 좋다.
- 1. IN 은 경우에 따라서 서브쿼리를 사용하여 보다 유연한 확장이 가능
- 2. IN 은 반드시 하나의 컬럼이 비교되어야 하지만 OR 는 여러 개의 컬럼이 올 수 있으므로 나중에 인덱스 구성에 대한 전략을 수립할 때도 IN 유리 하다.
- OR 연산자는 정말 다른 방법이 도저히 없는 경우를 제외하고는 사용하지 않는 것이 바람직하다.
1.2 AND 와 OR 의 비교
AND | OR |
---|
AND 연산자는 많이 사용할수록 집합의 정의역 범위를 줄여 준다. | OR 연산자는 많이 사용할 수록 집합의 정의역 범위를 크게 만든다. |
- 상기의 원칙은 연사자가 처리주관 조건에 사용되었을 때에 한해 적용됨
1.3 논리합 연산자의 실행계획
- 논리합 연산자를 처리주관 조건으로 할 때는 각각 별도의 최적화 실행계획으로 분리한 후 이들을 결합(Concatenation) 하는 실행 계획을 수립한다.
- 논리합 연산자가 사용된 처리주관 조건은 분리된 각 조건의 처리방법이 어느 한가지 방법의 진부분 집합이 되면 그 처리 방법에 귀속된다.
- 다시 말하면 어느 한쪽은 인덱스를 사용할 수 있으나, 어느 한쪽은 '전체 테이블 스캔'을 해야 한다면 이 실행계획은 두가지로 구분되지 않고 '전체 테이블 스캔'으로 통합된다는 것이다.
SELECT * FROM TAB1
WHERE A LIKE 'S%' OR B = '1';
'A' 칼럼과 'B' 칼럼이 각각 별도의 인덱스로 구성되어 있다면 이 SQL의 실행 계획은
다음과 같이 나타탈 수 있다.
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)
- 8i 이상에서는 INLIST ITERATOR 로 풀림 이 경우 /*\+ USE_CONCAT \*/ HINT 를 사용하면 CONCATENATION 으로 풀린다.
- PAGE 4-9 그림 [VLDB: 그림 Update 할것 ]
다음과 같은 사실이 발견된다.
- 비록 서로 다른 인덱스를 액세스하지만 마치 하나의 인덱스를 경유할 때와 큰 차이가 없다.
- 각각의 처리된 집합을 결합하기 위해서 별도의 머리 작업이나 정렬처리가 필요하지 않으므로 부분범위처리가 아직도 유효하다.
- 분기된 처리범위 간에 공통부분(교집합)이 많이 존재하면 불필요한 액세스가 일어난다.
- 실행계획 분리 시에는 WHERE 절에서 나중에 기술된 조건이 먼저 수행 된다는 점이다.
이것은 오라클에서만 나타나는 현상이다.
다음과 같은 특징이 발견된다.
- 정상적으로 실행계획의 분기가 일어난다면 OR 연산자도 거의 문제점을 가지지 않는다는 것
- 비록 OR 연산자를 사용했더라도 - 결합처리 실행계획이 수립되었다 하더라도 정렬작업 작업이 포함되지 않는다면 부분범위처리는 유효하다는 것
- 우리가 정상적으로 실행계획이 수립된 경우라도 OR 연산자의 사용이 불리한 경우를 보여 주고 있으며, 역으로 이와 같은 문제가 발생하지 않는다면 전혀 부담이 없음을 의미 한다.
- 추출될 집합을 정렬하지 않고서도 이들의 순서를 조절할수 있음을 뜻한다. WHERE 절의 기술 순서를 조정하여 정렬처리를 대신함으로써 정렬처리 없이 원하는 순서로 추출할 수도 있어 부분범위처리의 한가지 활용방법으로 이용할 수 있다.
1.4 OR 연산자 사용시의 주의사항
- 처리주관 조건으로 사용되는 OR 연산자는 적절한 실행계획만 수립된다면 문제점이 없다.
- 처리 주관조건이 아닐경우, 실행계획의 분기가 발생한 경우(Concatenation 발생)
OR 연산자 사용에 관한 유의 사항
- 데이터 모델링 단계에서 속성 결정시 OR 연산자 사용 가능성을 고려하라
- OR 의 적용범위를 가급적 축소하라
- 복잡한 OR는 옵티마이져에게 혼선을 유발시켜 의도치 않은 실행 계획을 만들수 있으므로 간단한 OR를 사용하라
- 상수쪽을 DECODE 등으로 가공하여 OR 연산자를 없애도록 노력하라
- 필터링(Filtering)을 통하여 분리 실행계획을 만들고자 할 경우에는 UNION ALL 을 사용하려 SQL을 분리하라
1.4.1 실행계획 분할 방법
- 입력된 조건값에 따라 처리주관 인덱스의 사용을 분리
- 전제조건 : 거래처의 평균 분포도는 1% 이하였지만 주거래 대상 업체인 '대한물산(거래처코드:101)'과의 거래는 전체의 60%가 넘는다.
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||'%')
- 정적 SQL의 파싱 원리를 제대로 이해하지 못했기 때문에 일어난 지극히 잘못된 방법이다.
- 사례와 같이 바인딩 변수(예. :IN_CUST)가 사용된 SQL문장을 파싱할 때는 변수인 상태로 실행계획을 수립하고, 실행되는 순간 입력된 상수값을 바인딩하여 실행한다.
- 다시 말해서 입력된 상수값을 감안하지 않고 변수 상태로 실행계획을 수립한다는 것이다.
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||'%'
1.4.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 ;
- 이 SQL 은 STATUS 컬럼이 인덱스를 가지고 있더라도 '전체 테이블 스캔'으로 실행계획이 작성된다.
- 그 이유는 단순하게 나열된 OR 연산자가 아니라, 위의 예처럼 OR 분기 내에 다시 OR가 분기된 복잡한 형태는 대부분 '전체 테이블 스캔' 방식으로 처리되기 때문이다.
SELECT CHULNO, CUSTNO, CHULDATE, UNCOST
FROM CHULGOT
WHERE STATUS LIKE DECODE(:SW,1,'2%')
OR STATUS LIKE DECODE(:SW,1,'1%','3%') ;
- 단순 OR 연산자로 대체하여 결합처리 방법으로 수행되었다.
- [VLDB: Concatenation ] NULL 은 반드시 'IS NULL'로 표현할 때만 NULL인 경우를 찾아주기 때문에 위의 예처럼 'STATUS LIKE NULL'로 된 조건은 NULL을 하나의 바인딩 상수로 간주하기 때문에 이 조건을 만족하는 처리범위는 언제나 존재 하지 않는다.
- 또한 처리 주관 조건이 'IS NULL' 이나 'IS NOT NULL'로 사용된 문장을 파싱할 때 인덱스 사용을 하지 않는 실행계획을 수립한다는 것이지 여기서와 같이 이미 실행계획이 수립된 후 바인딩 하는 과정에는 해당하지 않는다.
1.4.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 조건들은 모두 처리주관 조건의 자격을 상실하며 AND로 사용된 다른 칼럼도 인덱스를 가지지 않았으므로 이 SQL은 '전체 테이블 스캔'으로 처리 된다.
- 그 이유는 '인덱스 가능 OR 인덱스 가능 OR .. OR 인덱스 불가능' 의 논리합은 '인덱스 불가능'이기 때문이다.