ORACLE STUDYThe Logical Optimizer 스터8 ARTICLES
PUBLIC STUDY

The Logical Optimizer 스터디

The Logical Optimizer 책 스터디 한 내용을 정리한 글입니다.

01
2분 읽기

2-A. Heuristic Query Transformation 란?

Heuristic이란?

이 글의 노션 원문 ↗

Heuristic이란?

문제 해결에서 노력을 최소화하기 위한 고찰이나 규칙.

DB 옵티마이저는 Costing 없이 빠르게 처리하기 위해 Heuristic Rule을 사용함.

Query Transformation 목적:

SQL 성능을 빠르게 하기 위함.

꼭 Costing을 거치지 않고도 SQL을 단순화시켜 빠르게 처리할 수 있음.

예시 1: 불필요한 ORDER BY 제거

SQL
SELECT COUNT(*)
FROM tabl
ORDER BY col1;

COUNT(*)는 결과 한 줄이므로 ORDER BY는 불필요함.

이럴 때 옵티마이저는 ORDER BY를 제거함 → Heuristic Rule 적용 사례.

예시 2: 꼭 필요한 작업만 수행

SQL
SELECT a.고객번호, a.고객명
FROM 고객 a, 공동고유 b
WHERE a.고객구분코드 = b.고객구분코드(+);

필요한 컬럼만 추출하고, 조인을 최소화하여 불필요한 작업 생략.

단순히 고객번호와 고객명을 조회하는데 공통고유 테이블의 조인은 필요하지 않음

A 고객구분코드가 null인 데이터가 존재한다면 의미가 있음

이외의 상황에서는 Outer 조인으로 인하여 공통코드 테이블은 전혀 필요가 없음

고객과 공통코드 조인에서 공통코드가 필요 없는 경우 → 조인 제거.

Heuristic Transformation의 핵심

대부분의 규칙은 Costing 없이 작동.

SQL을 단순화하여 Hard Parsing의 부담을 줄이는 데 초점.

약 30가지 이상의 Transformation Rule 존재하며, 튜닝에 있어 정확한 이해가 필수.

Rule을 모르면 변경된 SQL 실행계획을 이해할 수 없음.

COMMENTS

이 글의 댓글

0

아직 댓글이 없습니다. 이 글에서 얻은 생각이나 질문을 남겨보세요.

02
2분 읽기

2-1. CSE (Common Subexpression Elimination): Where 절에서 or 사용시 중첩된 조건절은 제거하라

CSE는 Where 절에서 중복된 조건을 찾아 제거하는 최적화 기법.

이 글의 노션 원문 ↗

1. 개념

CSE는 Where 절에서 중복된 조건을 찾아 제거하는 최적화 기법.

OR 조건에서 중복된 공통 조건이 있을 경우, 이를 제거하여 불필요한 연산을 줄임.

2. 예시

SQL
SELECT department_id, salary
  FROM employee
 WHERE (department_id = 60 AND salary = 4200)
    OR (department_id = 60);

→ department_id = 60 조건이 중복되므로, 옵티마이저는 (department_id = 60)만 남기고 나머지를 제거.

3. 실행계획 및 Trace 분석

Predicate Information에 DEPARTMENT_ID=60만 남은 것을 통해 조건이 제거된 것을 확인.

10053 Trace에서도 CSE performed 로그로 확인 가능.

4. CSE 기능이 없으면?

SQL
ALTER SESSION SET "_eliminate_common_subexpr" = FALSE;

위와 같이 CSE 기능을 끄고 실행하면, 옵티마이저는 조건을 그대로 평가.

결과적으로 Full Table Scan이 발생하고 성능이 나빠짐.

5. 결론

CSE 기능은 조건절 최적화에 필수.

복잡한 SQL에서 중복된 조건을 제거하는 것이 성능 최적화의 핵심.

SQL 작성 시 중복 조건을 제거한 형태로 작성하는 것이 바람직함.

COMMENTS

이 글의 댓글

0

아직 댓글이 없습니다. 이 글에서 얻은 생각이나 질문을 남겨보세요.

03
10분 읽기

2-2. JE(Join Elimination) : 직접 사용하지 않는 테이블은 SQL에서 삭제하라

Join Elimination(조인 제거)은 실제로 사용되지 않는 테이블에 대한 조인을 자동으로 제거하여 SQL 성능을 향상시키는 기능.

이 글의 노션 원문 ↗

1. 개념

Join Elimination(조인 제거)은 실제로 사용되지 않는 테이블에 대한 조인을 자동으로 제거하여 SQL 성능을 향상시키는 기능.

주로 부모-자식 관계(FK)가 있는 테이블에서 사용되며, 자식 테이블에만 접근해도 충분할 경우 부모 테이블 액세스를 제거함.

2. 적용 예시

아래처럼 부모 테이블 PARENT와 자식 테이블 CHILD이 조인되어 있으나, 실제 조회는 CHILD만 사용한다면:

SQL
-- 원래 SQL
SELECT CHILD.COL1, CHILD.COL2
  FROM PARENT, CHILD
 WHERE PARENT.COL1 = CHILD.COL1;

-- FK 존재 시 JE 적용 후 SQL
SELECT CHILD.COL1, CHILD.COL2
  FROM CHILD
 WHERE CHILD.COL1 IS NOT NULL;

3. 조건

JE 기능이 정상 작동하려면 다음 조건들을 만족해야 함:

FK 존재: 자식 테이블에 부모를 참조하는 외래키(FK)가 정의되어 있어야 함

조건 단순성:

JOIN 조건이 = (Equal)일 것

Range, LIKE, BETWEEN, IN 등이 있을 경우 JE 미적용

PK 외 컬럼 사용 제한: 부모 테이블의 PK 외의 컬럼을 SELECT하면 JE 적용 불가

Multi-Column FK: FK가 복합 컬럼일 경우에도 JE 미적용

19c의 경우 멀티 컬럼 fk 여도 je 동작함

_optimizer_multicol_join_elimination = true 파라미터 영향, false로 변경시 10g와 동일하게 동작

SQL
-- 부모 테이블
CREATE TABLE tab_parent (
    col1 NUMBER,
    col2 NUMBER,
    col3 VARCHAR2(20),
    CONSTRAINT tab_parent_pk PRIMARY KEY (col1, col2)
);

-- 자식 테이블
CREATE TABLE tab_child (
    col1 NUMBER,
    col2 NUMBER,
    col4 VARCHAR2(20),
    CONSTRAINT tab_child_fk FOREIGN KEY (col1, col2) REFERENCES tab_parent (col1, col2)
);

INSERT INTO tab_parent VALUES (1, 10, 'parent_1');
INSERT INTO tab_parent VALUES (2, 20, 'parent_2');

INSERT INTO tab_child VALUES (1, 10, 'child_1');
INSERT INTO tab_child VALUES (2, 20, 'child_2');
COMMIT;

-- JE가 발생하지 않는지 확인할 SQL
SELECT 
       c.col1, c.col4
  FROM tab_child c, tab_parent p
 WHERE c.col1 = p.col1
   AND c.col2 = p.col2;

JE:[V2]   Considering Join Elimination on query block SEL$1 (#0)
*************************
Join Elimination [JE:R 12.2]
*************************
SQL:******* UNPARSED QUERY IS *******
SELECT "C"."COL1" "COL1","C"."COL4" "COL4" FROM "IMSI"."TAB_CHILD" "C","IMSI"."TAB_PARENT" "P" WHERE "C"."COL1"="P"."COL1" AND "C"."COL2"="P"."COL2"
JE:[V2] Query block (0x7f5da61a8f60) before join elimination:
SQL:******* UNPARSED QUERY IS *******
SELECT "C"."COL1" "COL1","C"."COL4" "COL4" FROM "IMSI"."TAB_CHILD" "C","IMSI"."TAB_PARENT" "P" WHERE "C"."COL1"="P"."COL1" AND "C"."COL2"="P"."COL2"
JE:[V2]: Try to eliminate P by ref. join elim using PRIMARY(COL1, COL2) <- FOREIGN(COL1, COL2)
JE:[V2]: Can eliminate P by ref. join elim using PRIMARY(COL1, COL2) <- FOREIGN(COL1, COL2)
JE:[V2] Eliminate table: TAB_PARENT (P)
JE:[V2] Query block (0x7f5da61a8f60) after join elimination:
SQL:******* UNPARSED QUERY IS *******
SELECT "C"."COL1" "COL1","C"."COL4" "COL4" FROM "IMSI"."TAB_CHILD" "C" WHERE "C"."COL1" IS NOT NULL AND "C"."COL2" IS NOT NULL
Registered qb: SEL$A43D1678 0xa61a8f60 (JOIN REMOVED FROM QUERY BLOCK SEL$1; SEL$1; "P"@"SEL$1")
---------------------
QUERY BLOCK SIGNATURE
---------------------
  signature (): qb_name=SEL$A43D1678 nbfros=1 flg=0
    fro(0): flg=0 objn=75035 hint_alias="C"@"SEL$1"

============
Plan Table
============
--------------------------------------+-----------------------------------+
| Id  | Operation          | Name     | Rows  | Bytes | Cost  | Time      |
--------------------------------------+-----------------------------------+
| 0   | SELECT STATEMENT   |          |       |       |     3 |           |
| 1   |  TABLE ACCESS FULL | TAB_CHILD|     2 |    76 |     3 |  00:00:01 |
--------------------------------------+-----------------------------------+

alter session set "_optimizer_multicol_join_elimination" = false;
alter session set statistics_level = all;
SELECT c.col1, c.col4
  FROM tab_child c, tab_parent p
 WHERE c.col1 = p.col1
and c.col2 = p.col2;
SELECT * FROM DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ADVANCED ALLSTATS LAST');
------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation          | Name          | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |               |      1 |        |       |     3 (100)|          |      2 |00:00:00.01 |       9 |
|   1 |  NESTED LOOPS      |               |      1 |      2 |   128 |     3   (0)| 00:00:01 |      2 |00:00:00.01 |       9 |
|   2 |   TABLE ACCESS FULL| TAB_CHILD     |      1 |      2 |    76 |     3   (0)| 00:00:01 |      2 |00:00:00.01 |       7 |
|*  3 |   INDEX UNIQUE SCAN| TAB_PARENT_PK |      2 |      1 |    26 |     0   (0)|          |      2 |00:00:00.01 |       2 |
------------------------------------------------------------------------------------------------------------------------------

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('19.1.0')
      DB_VERSION('19.1.0')
      OPT_PARAM('_optimizer_multicol_join_elimination' 'fa

ANSI JOIN 불가 (Oracle 10g): Oracle 10g에서는 ANSI JOIN 구문(JOIN ... ON)을 사용하면 JE 미적용

ANSI JOIN 사용 시 JE는 Oracle 11g부터 가능

4. 옵티마이저 힌트 관련

Oracle은 내부적으로 ELIMINATE_JOIN 힌트를 통해 JE를 수행함

NO_ELIMINATE_JOIN 힌트를 사용하면 JE 기능 비활성화

_optimizer_join_elimination_enabled = FALSE 파라미터로도 JE 기능 끌 수 있음

5. 결론

사용하지 않는 테이블은 제거하거나, FK를 정의하여 옵티마이저가 자동으로 제거하도록 유도하는 것이 성능 향상의 핵심

JE 조건을 만족하지 못하는 경우, SQL이나 데이터 모델을 개선할 필요가 있음

불필요한 조인 제거 = 불필요한 I/O 제거 = 성능 향상

COMMENTS

이 글의 댓글

0

아직 댓글이 없습니다. 이 글에서 얻은 생각이나 질문을 남겨보세요.

04
5분 읽기

2-3. OE (Outer Join Table Elimination) : 불필요한 Outer쪽 테이블은 삭제하라

Oracle 11g부터 추가된 기능으로, OUTER JOIN에서 실제 사용되지 않는 테이블을 자동 제거하여 SQL 성능을 향상시키는 기능

이 글의 노션 원문 ↗

1. 개념

Oracle 11g부터 추가된 기능으로, OUTER JOIN에서 실제 사용되지 않는 테이블을 자동 제거하여 SQL 성능을 향상시키는 기능

JE(Join Elimination)과 유사하지만, PK/FK 제약 조건 없이도 적용 가능

2. 예시

SQL
SELECT e.empno, e.ename, e.deptno
  FROM emp e, dept d
 WHERE e.deptno = d.deptno(+);

위 SQL은 outer 조인이지만, 실제로는 employee 테이블만 사용하므로 department 테이블은 제거 가능

실행계획과 Outline Data에서 ELIMINATE_JOIN 힌트 확인 가능 → OE 기능 적용 확인

3. JE와의 차이점

4. 제약 사항

OE도 JE와 동일한 제약사항이 존재:

SELECT절에 OUTER 테이블의 컬럼이 있으면 제거 불가

WHERE절에 OUTER 테이블 조건이 있으면 제거 불가

예시 (OE 미적용)

SQL
SELECT e.empno, e.ename, e.deptno
  FROM emp e, dept d
 WHERE e.deptno= d.deptno(+)
   AND d.loc(+) = 'DALLAS';

d.manager_id는 결과에 영향을 주지 않지만, WHERE절에서 사용되었기 때문에 OE 미적용

예시 (OE 적용)

11g 에서는 oe가 동작하지 않았지만 19c에선 동작함(JOIN REMOVED FROM 라고 표시됨)

SQL
JE:[V2]   Considering Join Elimination on query block SEL$1 (#0)
*************************
Join Elimination [JE:R 12.2]
*************************
SQL:******* UNPARSED QUERY IS *******
SELECT "E"."EMPNO" "EMPNO","E"."ENAME" "ENAME","E"."DEPTNO" "DEPTNO" FROM "IMSI"."EMP" "E","IMSI"."DEPT" "D" WHERE "E"."DEPTNO"="D"."DEPTNO"(+) AND "D"."LOC"(+)='DALLAS'
JE:[V2] Query block (0x7fad40b30f60) before join elimination:
SQL:******* UNPARSED QUERY IS *******
SELECT "E"."EMPNO" "EMPNO","E"."ENAME" "ENAME","E"."DEPTNO" "DEPTNO" FROM "IMSI"."EMP" "E","IMSI"."DEPT" "D" WHERE "E"."DEPTNO"="D"."DEPTNO"(+) AND "D"."LOC"(+)='DALLAS'
JE:[V2]: Try to eliminate D by outer join elim using PRIMARY(DEPTNO)
JE:[V2]: Can eliminate D by outer join elim using PRIMARY(DEPTNO)
JE:[V2] Eliminate table: DEPT (D)
JE:[V2] Query block (0x7fad40b30f60) after join elimination:
SQL:******* UNPARSED QUERY IS *******
SELECT "E"."EMPNO" "EMPNO","E"."ENAME" "ENAME","E"."DEPTNO" "DEPTNO" FROM "IMSI"."EMP" "E"
Registered qb: SEL$F7859CDE 0x40b30f60 (JOIN REMOVED FROM QUERY BLOCK SEL$1; SEL$1; "D"@"SEL$1")
---------------------
QUERY BLOCK SIGNATURE
---------------------
  signature (): qb_name=SEL$F7859CDE nbfros=1 flg=0
    fro(0): flg=0 objn=74089 hint_alias="E"@"SEL$1"  

CVM: Considering view merge in query block SEL$F7859CDE (#0)
OJE: Begin: find best directive for query block SEL$F7859CDE (#0)
OJE: OJ to IJ validity check falied.
OJE: End: finding best directive for query block SEL$F7859CDE (#0)
query block SEL$1 transformed to SEL$F7859CDE (#0)
Considering Query Transformations on query block SEL$F7859CDE (#0)

5. 결론

불필요한 OUTER JOIN 제거는 I/O를 줄이고 성능 향상에 매우 유효

가능한 경우, OUTER 테이블을 제거하는 방향으로 SQL 작성하거나, 옵티마이저에 의해 자동 제거되도록 유도

미세한 조건으로 OE가 적용되지 않는 문제는 향후 개선이 기대됨⇒ 정확히 어떤 버전부터인지는 알수없지만 19c에서는 개선되어있음

COMMENTS

이 글의 댓글

0

아직 댓글이 없습니다. 이 글에서 얻은 생각이나 질문을 남겨보세요.

05
4분 읽기

2-4. OJE(Outer-Join Elimination) : 의미 없는 Outer 조인을 Inner 조인으로 바꾸어라

OJE는 Oracle 옵티마이저가 불필요한 Outer Join을 분석하여 Inner Join으로 자동 변경하는 기능

이 글의 노션 원문 ↗

1. 개념

OJE는 Oracle 옵티마이저가 불필요한 Outer Join을 분석하여 Inner Join으로 자동 변경하는 기능

OE는 Outer 쪽 테이블 자체를 제거하는 기능이라면, OJE는 조인의 성격 자체를 바꾸는 기능

이 기능은 Oracle V7부터 존재, 특별한 제약 없이 자동 적용됨

2. 적용 예시

SQL
SELECT e.empno, e.ename, e.deptno, d.dname
  FROM emp e, dept d
 WHERE e.deptno= d.deptno(+)
and d.loc='DALLAS';

d.location_id = 1700 조건은 Outer Join의 의미를 무력화시킴

실행계획에서 HASH JOIN OUTER가 아닌 HASH JOIN으로 변경됨 → Inner Join으로 바뀐 것

Outline에는 OUTER_JOIN_TO_INNER 힌트 포함됨

10053 Trace에서도 OJE 적용 여부 확인 가능(Converting outer join of DEPT and EMP to inner-join. 및 OUTER-JOIN REMOVED 부분)

SQL
OJE: Begin: find best directive for query block SEL$1 (#0)
OJE: Considering outer-join elimination on query block SEL$1 (#0)
OJE: considering predicate"E"."DEPTNO"="D"."DEPTNO"(+)

rejected
OJE: considering predicate"D"."LOC"='DALLAS'

OJE:      Converting outer join of DEPT and EMP to inner-join.
considered
Registered qb: SEL$AF7D52CD 0x48c47f60 (OUTER-JOIN REMOVED FROM QUERY BLOCK SEL$1; SEL$1; "D"@"SEL$1")
---------------------
QUERY BLOCK SIGNATURE
---------------------
  signature (): qb_name=SEL$AF7D52CD nbfros=2 flg=0
    fro(0): flg=0 objn=74087 hint_alias="D"@"SEL$1"
    fro(1): flg=0 objn=74089 hint_alias="E"@"SEL$1"

OJE:   outer-join eliminated
OJE: End: finding best directive for query block SEL$AF7D52CD (#0)

3. 실패 사례

아래처럼 IS NULL 조건이 붙은 경우, OJE는 적용되지 않음

SQL
SELECT e.empno, e.ename, e.deptno, d.dname
  FROM emp e, dept d
 WHERE e.deptno= d.deptno(+)
And d.deptno is null;

이유: IS NULL 조건은 Outer Join 결과에서만 판단 가능하기 때문

Trace에서도 OJE: rejected 확인 가능

SQL
OJE: Begin: find best directive for query block SEL$1 (#0)
OJE: Considering outer-join elimination on query block SEL$1 (#0)
OJE: considering predicate"E"."DEPTNO"="D"."DEPTNO"(+)

rejected
OJE: considering predicate"D"."DEPTNO" IS NULL

rejected
OJE:   outer-join not eliminated
OJE: End: finding best directive for query block SEL$1 (#0)

4. 주의사항

OJE가 실패하는 IS NULL 조건이 붙은 경우, 옵티마이저는 Anti Join 전략으로 우회하여 처리 가능

IS NULL이 Outer 테이블에 붙으면 Outer Join 유지 필요

OJE는 성능 향상에 기여하는 자동 변환 기능으로, SQL 작성 시 의도치 않게 Outer Join을 유지하지 않도록 주의

5. 결론

COMMENTS

이 글의 댓글

0

아직 댓글이 없습니다. 이 글에서 얻은 생각이나 질문을 남겨보세요.

06
6분 읽기

2-5. OBYE(Order By Elimination) : 불필요한 Order By를 삭제하라

OBYE는 Oracle 옵티마이저가 사용되지 않는 ORDER BY 절을 자동으로 제거하여 성능을 향상시키는 기능

이 글의 노션 원문 ↗

1. 개념

OBYE는 Oracle 옵티마이저가 사용되지 않는 ORDER BY 절을 자동으로 제거하여 성능을 향상시키는 기능

Oracle 10g부터 도입, ORDER BY가 정렬 결과에 불필요한 경우 제거함

2. 적용 예시

SQL
SELECT e.deptno, COUNT(*) cnt
  FROM emp e,
       (SELECT d.deptno, d.dname
          FROM dept d
         ORDER BY d.deptno) d
 WHERE e.deptno = d.deptno
   AND e.job = 'CLERK'
 GROUP BY e.deptno;

인라인 뷰 d에 ORDER BY가 있지만, 바깥쪽 쿼리에서 정렬이 요구되지 않음

옵티마이저는 VIEW 내부의 ORDER BY 절을 제거 → OBYE 발생

불필요한 SORT 제거 → PGA 소비 절감, 성능 향상

*위 쿼리 사용시 JE 발생하여 d테이블을 읽지 않음, select 절에 d.name을 넣어 obye 확인

SQL
SELECT e.deptno, d.dname, COUNT(*) cnt
  FROM emp e,
       (SELECT d.deptno, d.dname
          FROM dept d
         ORDER BY d.deptno) d
 WHERE e.deptno = d.deptno
   AND e.job = 'CLERK'
 GROUP BY e.deptno;

3. 실행계획 및 확인

실행계획(Execution Plan)에 SORT ORDER BY가 없음 → OBYE 적용 확인

Outline 정보에서 ELIMINATE_OBY 힌트 확인 가능

10053 Trace:

"Order not used"

"OBYE: OBYE performed." → 적용 성공 메시지

SQL
OBYE:   Considering Order-by Elimination from view SEL$1 (#0)
***************************
Order-by elimination (OBYE)
***************************
OBYE:   Considering Order-by Elimination from view SEL$2 (#0)
***************************
Order-by elimination (OBYE)
***************************
OBYE: Removing order by from query block SEL$2 (#0) (order not used)
Registered qb: SEL$73523A42 0xa6425988 (ORDER BY REMOVED FROM QUERY BLOCK SEL$2; SEL$2)
---------------------
QUERY BLOCK SIGNATURE
---------------------
  signature (): qb_name=SEL$73523A42 nbfros=1 flg=0
    fro(0): flg=0 objn=74087 hint_alias="D"@"SEL$2"

OBYE:     OBYE performed.
OBYE:     OBYE bypassed: no order by to eliminate.

============
Plan Table
============
--------------------------------------------------+-----------------------------------+
| Id  | Operation                       | Name    | Rows  | Bytes | Cost  | Time      |
--------------------------------------------------+-----------------------------------+
| 0   | SELECT STATEMENT                |         |       |       |     5 |           |
| 1   |  HASH GROUP BY                  |         |     1 |    31 |     5 |  00:00:01 |
| 2   |   HASH JOIN                     |         |     1 |    31 |     4 |  00:00:01 |
| 3   |    NESTED LOOPS                 |         |     1 |    31 |     4 |  00:00:01 |
| 4   |     NESTED LOOPS                |         |     1 |    31 |     4 |  00:00:01 |
| 5   |      STATISTICS COLLECTOR       |         |       |       |       |           |
| 6   |       TABLE ACCESS FULL         | EMP     |     1 |    19 |     3 |  00:00:01 |
| 7   |      INDEX UNIQUE SCAN          | PK_DEPT |     1 |       |     0 |           |
| 8   |     TABLE ACCESS BY INDEX ROWID | DEPT    |     1 |    12 |     1 |  00:00:01 |
| 9   |    TABLE ACCESS FULL            | DEPT    |     1 |    12 |     1 |  00:00:01 |
--------------------------------------------------+-----------------------------------+

플랜 중 STATISTICS COLLECTOR 는 12c 부터 나온 Adaptive Query Optimization 기능이라고함

4. 제어 방법

5. 결론

OBYE는 ORDER BY가 최종 출력 결과에 영향을 주지 않는 경우, SQL 성능을 향상시키기 위해 자동 제거됨

특히 서브쿼리나 뷰에서의 ORDER BY는 불필요할 가능성이 높음

제거되지 않으면 SORT 작업과 PGA 메모리 사용이 증가하므로, 최적화 여부를 반드시 점검할 것

COMMENTS

이 글의 댓글

0

아직 댓글이 없습니다. 이 글에서 얻은 생각이나 질문을 남겨보세요.

07
4분 읽기

2-6. DE (Distinct Elimination) : 불필요한 Distinct를 제거하라

Select절에 Unique 컬럼이 포함되어 있을 경우, 불필요한 DISTINCT를 옵티마이저가 자동 제거하는 기능

이 글의 노션 원문 ↗

1. 개념

Select절에 Unique 컬럼이 포함되어 있을 경우, 불필요한 DISTINCT를 옵티마이저가 자동 제거하는 기능

Sort/Hash Unique 작업이 불필요하므로 성능 개선

Oracle 11g부터 도입

2. 적용 예시

SQL
SELECT DISTINCT d.deptno, e.empno
  FROM dept d, emp e
 WHERE d.deptno= e.deptno;

두 테이블 모두 PK를 SELECT절에 포함하고 있어 결과는 자동으로 유일

Plan상에 SORT UNIQUE, HASH UNIQUE 없음 → DISTINCT 제거됨

Trace: Eliminated SELECT DISTINCT from query block

SQL
***************************
OBYE:     OBYE bypassed: no order by to eliminate.
Eliminated SELECT DISTINCT from query block SEL$1 (#0)
JE:[V2]   Considering Join Elimination on query block SEL$1 (#0)
*************************

3. 제약 및 주의사항

DE가 발생하지 않는 경우

SELECT 절에 Unique 컬럼이 하나 빠진 경우

WHERE 조건에 따라 유일성이 깨질 수 있는 경우

테이블에 PK는 삭제하고 Unique Index만 존재할 경우 (Null 허용 가능성이 있어)

Not Null 조건이 빠지면 DE 비적용

파라미터

DE: _optimizer_distinct_elimination (Default: TRUE)

DEU(Distinct Elimination using Unique Index)는 제어 불가, 옵티마이저 판단에 따름

4. DE vs DEU 차이

5. 추가 설명

DEU는 SELECT절에 Unique 컬럼이 없어도 Plan이 INDEX UNIQUE SCAN이면 제거 수행

옵티마이저가 판단을 잘못하면 Full Table Scan 후 Hash Unique가 발생 → 심각한 성능 저하

DEU는 무조건 우월한 기능이 아님 → 불필요한 DISTINCT 남발은 피해야 함

6. 실무 적용 시 주의사항

옵티마이저가 DEU 기능을 잘못 적용하면 대용량 쿼리에서 전체 집합 정렬(Sort Unique) 발생 → 치명적

서브쿼리 Unnesting과 함께 동작하여 쿼리 구조가 변화할 수 있음

DISTINCT를 명시적으로 사용할 때는 반드시 필요성 검토 후 사용

참고용 : elimination 이 들어간 히든파라미터 목록(19c)

SQL
name                                               desc
-------------------------------------------------- ----------------------------------------------------------------------
_column_elimination_off                            turn off predicate-only column elimination
_optimizer_distinct_elimination                    Eliminates redundant SELECT DISTNCT's
_optimizer_enhanced_join_elimination               Enhanced(12.2) join elimination
_optimizer_join_elimination_enabled                optimizer join elimination enabled
_optimizer_multicol_join_elimination               eliminate multi-column key based joins
_optimizer_order_by_elimination_enabled            Eliminates order bys from views before query transformation
_predicate_elimination_enabled                     allow predicate elimination if set to TRUE
_sort_elimination_cost_ratio                       cost ratio for sort eimination under first_rows mode
COMMENTS

이 글의 댓글

0

아직 댓글이 없습니다. 이 글에서 얻은 생각이나 질문을 남겨보세요.

08
2분 읽기

2.7 CNT(Count(column) To Count(*) ) : Count(컬럼) 사용시 해당 컬럼이 Not Null인 경우 Count(*) 로 대체하라

Count(컬럼) 사용 시, 해당 컬럼이 Not Null 제약이 있으면 Oracle 옵티마이저가 이를 자동으로 Count(*)로 변환

이 글의 노션 원문 ↗

1. 개념

Count(컬럼) 사용 시, 해당 컬럼이 Not Null 제약이 있으면 Oracle 옵티마이저가 이를 자동으로 Count(*)로 변환

이 기능을 CNT Transformation이라고 하며, 성능 최적화를 위해 도입됨

2. 적용 예시 (CNT 작동됨)

SQL
SELECT e.department_id, COUNT(e.last_name) cnt
  FROM employee e
 WHERE e.job_id = 'ST_CLERK'
GROUP BY e.department_id;

LAST_NAME은 Not Null 컬럼

인덱스만으로 처리 가능하지만 일반적으로는 테이블 액세스가 필요

CNT 작동 → Count(*)로 자동 변환되어 인덱스만으로 처리

실행계획에 테이블 액세스가 없음

Trace에도: Converting COUNT(LAST_NAME) to COUNT(*) 메시지 확인 가능

3. 비적용 예시 (CNT 작동 안됨)

SQL
SELECT e.department_id, COUNT(e.first_name) cnt
  FROM employee e
 WHERE e.job_id = 'ST_CLERK'
GROUP BY e.department_id;

FIRST_NAME은 Null 허용 컬럼

CNT 기능 작동하지 않음 → 테이블 액세스 발생

4. 결론

작동 조건 : Count 대상 컬럼이 Not Null 제약이 명시되어 있어야 함

작동 효과 : Count(*)로 자동 변환 → 테이블 액세스 생략 가능

모델링 시 권장 : Not Null 컬럼은 반드시 Constraint 명시

추가 참고 : Count(*)는 Count(컬럼)보다 일반적으로 더 빠름

목적 : Hard Parsing과 Query Transformation의 부하를 줄이고 성능 최적화

COMMENTS

이 글의 댓글

0

아직 댓글이 없습니다. 이 글에서 얻은 생각이나 질문을 남겨보세요.

3S
나도 공부 기록을 꺼내볼까요?

공개 노션 링크 하나면 목차가 있는 스터디가 됩니다.

내 글 공유하기 →