TIL
  • Contents
  • Book
    • 도메인 주도 설계
      • 1. 동작하는 도메인 모델 만들기
    • 오브젝트
      • 데이터 중심 설계
      • 책임 중심 설계
      • 책임 할당을 위한 GRASP 패턴
      • 메시지와 인터페이스
      • 객체 분해
    • Effective Java
      • Item 7 - 다 쓴 객체 참조를 해제하라
      • Item 7 발표 내용
      • Item 13 - clone 재정의는 주의해서 진행하라
      • Item 13 발표 내용
      • Item 16 - public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
      • Item 16 발표 내용
      • Item 26 - 로 타입은 사용하지 말라
      • Item 28 - 배열보다는 리스트를 사용하라
      • Item 28 발표 내용
      • Item 29 - 이왕이면 제네릭 타입으로 만들라
      • Item 30 - 이왕이면 제네릭 메서드로 만들라
      • Item 31 - 한정적 와일드 카드를 사용해 API 유연성을 높이라
      • Item 35 - ordinal 메서드 대신 인스턴스 필드를 사용하라
      • Item 37 - ordinal 인덱싱 대신 EnumMap을 사용하라
      • Item 37 발표 내용
      • Item 43 - 람다보다는 메서드 참조를 사용하라
      • Item 43 발표 정리
      • Item 56 - 공개된 API 요소에는 항상 문서화 주석을 작성하라
      • Item 56 발표 정리
      • Item 62 - 다른 타입이 적절하다면 문자열 사용을 피하라
      • Item 62 발표 정리
      • Item 73 - 추상화 수준에 맞는 예외를 던지라
      • Item 83 - 지연 초기화는 신중히 사용하라
      • Item 83 발표 내용
      • Item 89 - 인스턴스 수를 통제해야 한다면 readResolve보다는 열거 타입을 사용하라
      • Item 89 발표 내용
    • 개발자를 위한 SQL 튜닝
      • SQL 쿼리 실습을 위한 DB 서버 구축
      • 인덱스 튜닝
      • 인덱스 스캔 튜닝
      • 인덱스 스캔 튜닝 실습
      • 인덱스 패스트 풀 스캔
      • 테이블 풀 스캔 튜닝
      • 조인 튜닝
      • 중첩 루프 조인 튜닝
      • 중첩 루프 조인 튜닝 실습
      • 해시 조인 튜닝
      • 해시 조인 튜닝 실습
      • 세미 조인 튜닝
      • 세미 조인 튜닝 실습
      • 아우터 조인
      • 함수 튜닝
      • 부분 범위 처리 튜닝
      • 파티셔닝 튜닝
      • 파티션 인덱스 튜닝
      • 병렬 처리 튜닝
  • Java
    • Design Pattern
      • Intro
      • Types of Design Patterns
      • Creational
        • Builder Pattern
        • Singleton Pattern
        • Prototype Pattern
        • Factory Pattern
        • Abstract Factory Pattern
      • Structural
        • Adapter Pattern
        • Bridge Pattern
        • Composite Pattern
        • Decorator Pattern
        • Facade Pattern
        • Flyweight Pattern
        • Proxy Pattern
      • Behavioural
        • Chain of Responsibility Pattern
        • Command Pattern
        • Interpreter Pattern
        • Iterator Pattern
        • Mediator Pattern
        • Memento Pattern
        • Observer Pattern
        • State Pattern
        • Strategy Pattern
        • Template Method Pattern
        • Visitor Pattern
    • Java
      • Cracking the Coding Interview
      • TDD, Clean Code with Java 11기
        • 자동차 레이싱
        • 로또
        • 사다리 타기
        • 볼링 게임 점수판
    • 궁금증
      • 자바 8 버전의 인터페이스와 추상클래스
      • 자바의 제네릭은 어떻게 이전 버전과 호환되는 걸까?
      • 스프링 MVC 기본 구조
      • 마샬링과 직렬화
      • 인터뷰 질문 모음
      • Code Coverage
  • Database
    • Database
      • SQL 레벨업
      • DB 스터디
        • DBMS
          • MySQL
        • INDEX
        • Join(Nested Loop, Hash)
        • Join(Semi, Outer)
        • Partial Range Processing
        • Function
        • Partitioning
        • Parallel Processing
  • Network
  • Architecture
    • Issue
      • Git Push Error
      • SonarLint Warning - assertThatExceptionOfType()
  • Infra
  • Spring
    • Spring JPA
      • 1. 데이터 모델링 및 연관관계 설정
      • 2. 최적화 내용
      • 3. Spring-Data-Jpa
      • 4. Query DSL
    • Spring Security
      • Intro
    • Spring Batch
      • 배치용 디비 설치
      • 배치 데이터 분석하기
      • 배치 프로세스 구상하기 및 성능 차이 확인하기
  • Issue
  • Tistory
    • Tistory Blog
  • Design High Performing Architectures
  • Design Resilient Architectures
  • Design Secure Applications And Architectures
  • Design Cost-Optimized Architectures
Powered by GitBook
On this page
  • Intro
  • 데이터 준비
  • 해시 조인으로 성능 극대화
  • 인라인 뷰를 이용한 해시 조인으로 성능 극대화

Was this helpful?

  1. Book
  2. 개발자를 위한 SQL 튜닝

해시 조인 튜닝 실습

해시 조인 튜닝

Previous해시 조인 튜닝Next세미 조인 튜닝

Last updated 3 years ago

Was this helpful?

내용에서 참고한 내용입니다.

Intro

  • 상품과 주문에 대한 모델링

  • 상품 정보 10만건 생성, 주문정보 500만건 생성

  • 하나의 제품은 0개 또는 1개 이상의 주문을 가질 수 있다.

  • 하나의 주문은 반드시 한 개의 제품을 갖는다.

  • Outer 테이블(주문 정보)과 Inner 테이블(상품 정보)을 설정하여 중첩 루프 조인으로 쿼리 실행 확인

    • 대용량 테이블인 주문 정보 테이블이 Outer 테이블로 지정되어 매우 큰 성능 부하 확인

  • 튜닝 후 상품 테이블을 Build Input로 지정, FULL 힌트를 통해 Build Input을 테이블 풀 스캔으로 유도

    • USE_HASH 힌트로 주문정보 테이블을 Probe Input으로 지정하여 해시 조인을 수행

데이터 준비

  • 상품, 주문 정보 테이블 생성

CREATE TABLE TB_PRDT
(
    PRDT_CD  VARCHAR2(6),  -- 상품코드
    PRDT_NM  VARCHAR2(50), -- 상품명
    REL_DT   VARCHAR2(8),  -- 출시일자
    COST_AMT NUMBER(15),   -- 원가금액
    INST_DTM DATE,         -- 입력시간
    INST_ID  VARCHAR2(50), -- 입력자ID
    UPDT_DTM DATE,         -- 수정시간
    UPDT_ID  VARCHAR2(5)   -- 수정자ID
);

CREATE TABLE TB_ORD
(
    ORD_NO   VARCHAR2(10),  -- 주문번호
    ORD_DT   VARCHAR2(8),   -- 주문일자
    ORD_NM   VARCHAR2(150), -- 주문이름
    ORD_AMT  NUMBER(15),    -- 주문금액
    PRDT_CD  VARCHAR2(6),   -- 상품코드
    SALE_GB  VARCHAR2(2),   -- 판매구분
    PAY_GB   VARCHAR2(2),   -- 결제구분
    CUST_ID  VARCHAR2(10),  -- 고객ID
    INST_DTM DATE,          -- 입력시간
    INST_ID  VARCHAR2(50),  -- 입력자ID
    UPDT_DTM DATE,          -- 수정시간
    UPDT_ID  VARCHAR2(5)    -- 수정자ID
);
  • 상품 정보 더미 데이터 생성

INSERT INTO TB_PRDT
SELECT LPAD(TO_CHAR(ROWNUM), 6, '0'),
       DBMS_RANDOM.STRING('U', 50),
       TO_CHAR(SYSDATE - TRUNC(DBMS_RANDOM.VALUE(1, 3650)), 'YYYYMMDD'),
       TRUNC(DBMS_RANDOM.VALUE(100, 100000)),
       SYSDATE,
       'DBMSEXPERT',
       NULL,
       NULL
FROM DUAL
CONNECT BY LEVEL <= 100000;

COMMIT;
  • 상품 정보에 연관된 주문 정보 더미 데이터 생성

CREATE TABLE DUAL_10
(
    DUMMY VARCHAR2(1)
);

INSERT INTO DUAL_10
SELECT DUMMY
FROM DUAL
CONNECT BY LEVEL <= 10;

COMMIT;

ALTER TABLE TB_ORD
    NOLOGGING;

INSERT /*+ APPEND */ INTO TB_ORD --APPEND 힌트 사용
SELECT LPAD(TO_CHAR(ROWNUM), 10, '0'),
       TO_CHAR(SYSDATE - TRUNC(DBMS_RANDOM.VALUE(1, 3650)), 'YYYYMMDD'),
       DBMS_RANDOM.STRING('U', 150),
       TRUNC(DBMS_RANDOM.VALUE(1000, 100000)),
       LPAD(TO_CHAR(TRUNC(DBMS_RANDOM.VALUE(1, 100000))), 6, '0'),
       LPAD(TO_CHAR(MOD(TRUNC(DBMS_RANDOM.VALUE(1, 1000)), 3)), 2, '0'),
       LPAD(TO_CHAR(MOD(TRUNC(DBMS_RANDOM.VALUE(1, 1000)), 10)), 2, '0'),
       LPAD(TO_CHAR(TRUNC(DBMS_RANDOM.VALUE(1, 10000))), 10, '0'),
       SYSDATE,
       'DBMSEXPERT',
       NULL,
       NULL
FROM DUAL_10,
     (SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 500000);

-- [2021-07-29 16:22:14] 5,000,000 rows affected in 21 m 34 s 644 ms
COMMIT;
  • 제약 조건 생성

ALTER TABLE TB_PRDT
ADD CONSTRAINT TB_PRDT_PK
PRIMARY KEY (PRDT_CD);

ALTER TABLE TB_ORD
ADD CONSTRAINT TB_ORD_PK
PRIMARY KEY (ORD_NO);

ALTER TABLE TB_ORD
ADD CONSTRAINT TB_ORD_FK
FOREIGN KEY (PRDT_CD) REFERENCES TB_PRDT(PRDT_CD);

CREATE INDEX TB_ORD_IDX01 ON TB_ORD(ORD_DT);
  • 통계 정보 생성

ANALYZE TABLE TB_PRDT COMPUTE STATISTICS
FOR TABLE FOR ALL INDEXES FOR ALL INDEXED COLUMNS SIZE 254;

ANALYZE TABLE TB_ORD COMPUTE STATISTICS
FOR TABLE FOR ALL INDEXES FOR ALL INDEXED COLUMNS SIZE 254;

해시 조인으로 성능 극대화

SELECT /*+ LEADING(A) INDEX(A TB_ORD_IDX01) USE_NL(B) */
    A.ORD_NO,
    A.ORD_DT,
    B.PRDT_CD,
    B.PRDT_NM
FROM TB_ORD A,
     TB_PRDT B
WHERE A.ORD_DT > TO_CHAR(SYSDATE - 365, 'YYYYMMDD')
  AND A.PRDT_CD = B.PRDT_CD;

-- [2021-07-29 16:24:25] 499,510 rows retrieved starting from 1 in 30 s 110 ms (execution: 14 ms, fetching: 30 s 96 ms)
  • SQL 분석

    • LEADING 힌트를 이용하여 주문 정보 테이블을 Outer 테이블로 지정

    • Outer 테이블을 스캔 시 TB_ORD_IDX01 인덱스를 이용

    • USE_NL 힌트를 이용하여 상품정보 테이블을 Inner 테이블로 지정하여 중첩 루프 조인을 수행하도록 유도한다.

  • SQL의 문제점

    • 대용량 테이블인 주문정보 테이블이 outer 테이블로 지정되어 매우 큰 성능 부하가 예상된다.

    • 주문일자 컬럼 조건만으로는 대량의 행이 나오기 때문에 그에 따른 테이블 랜덤 액세스 부하가 발생한다.

  • Execute Plan

    • TB_ORD_IDX01을 인덱스 범위 스캔(INDEX RANGE SCAN)한다.

    • TB_ORD_IDX01의 ROWID를 이용하여 TB_ORD 테이블을 테이블 랜덤 액세스(TABLE ACCESS BY INDEX ROWID)한다.

    • TB_ORD 테이블의 PRDT_CD 컬럼 값을 이용하여 TB_PRDT_PK 인덱스를 인덱스 유일 스캔(INDEX UNIQUE SCAN)한다.

    • 3번과 5번의 연산을 중첩 루프 조인(NESTED LOOPS)를 수행한다.

    • TB_PRDT_PK 에서 나온 ROWID를 이용하여 TB_PRDT 테이블을 테이블 랜덤 액세스(TABLE ACCESS BY INDEX ROWID)한다.

    • 중첩 루프 조인과 TB_PRDT 테이블을 테이블 랜덤 액세스(TABLE ACCESS BY INDEX ROWID)한다.

    • SELECT 절의 연산을 수행한다.

SELECT /*+ LEADING(B) FULL(B) USE_HASH(A)  */
    A.ORD_NO,
    A.ORD_DT,
    B.PRDT_CD,
    B.PRDT_NM
FROM TB_ORD A,
     TB_PRDT B
WHERE A.ORD_DT > TO_CHAR(SYSDATE - 365, 'YYYYMMDD')
  AND A.PRDT_CD = B.PRDT_CD;

-- [2021-07-29 16:28:31] 499,510 rows retrieved starting from 1 in 16 s 429 ms (execution: 78 ms, fetching: 16 s 351 ms)
  • SQL 분석

    • LEADING 힌트를 이용하여 작은 집합인 상품 테이블을 Build Input으로 지정

    • FULL 힌트를 이용하여 Build Input을 테이블 풀 스캔으로 유도

    • USE_HASH 힌트를 이용하여 주문 테이블을 Probe Input으로 지정하고 해시 조인을 수행

  • Execute Plan

    • TB_PRDT 테이블을 테이블 풀 스캔(TABLE ACCESS FULL)을 수행

    • TB_ORD 테이블을 테이블 풀 스캔(TABLE ACCESS FULL)을 수행

    • TB_PRDT 테이블을 Build Input, TB_ORD 테이블을 Probe Input으로 하여 해시 조인(Hash Join)을 수행

    • TB_PRDT 테이블을 Build Input, TB_ORD 테이블을 Probe Input으로 하여 해시 조인(Hash Join)을 수행

인라인 뷰를 이용한 해시 조인으로 성능 극대화

CREATE TABLE TB_PRDT
(
    PRDT_CD  VARCHAR2(6),  --상품코드
    PRDT_NM  VARCHAR2(50), --상품명
    INST_DTM DATE,         --입력일시
    INST_ID  VARCHAR2(50)  --입력자
);

CREATE TABLE TB_PRDT_SALE_DAY
(
    SALE_NO  VARCHAR2(10), --판매번호
    SALE_DT  VARCHAR2(8),  --판매일자
    SALE_TM  VARCHAR2(6),  --판매시간
    SALE_CNT NUMBER(9),    --판매수
    SALE_AMT NUMBER(9),    --판매금액
    PRDT_CD  VARCHAR2(6),  --제품코드
    INST_DTM DATE,         --입력일시
    INST_ID  VARCHAR2(50)  --입력자
);
  • 상품 정보 생성하기

INSERT INTO TB_PRDT
SELECT LPAD(TO_CHAR(ROWNUM), 6, '0'),
       DBMS_RANDOM.STRING('U', 50),
       SYSDATE,
       'DBMSEXPERT'
FROM DUAL
CONNECT BY LEVEL <= 100000;

COMMIT;
-- [2021-07-29 16:32:25] 1,000,000 rows affected in 1 m 30 s 42 ms
  • 더미 테이블 생성하기

CREATE TABLE DUAL_10
(
    DUMMY VARCHAR2(1)
);

INSERT INTO DUAL_10
SELECT DUMMY
FROM DUAL
CONNECT BY LEVEL <= 10;

COMMIT;
  • 주문정보 테이블 생성하기

ALTER TABLE TB_PRDT_SALE_DAY
    NOLOGGING;

INSERT /*+ APPEND */ INTO TB_PRDT_SALE_DAY --APPEND 힌트 사용
SELECT LPAD(TO_CHAR(ROWNUM), 10, '0'),
       TO_CHAR(SYSDATE - DBMS_RANDOM.VALUE(1, 3650), 'YYYYMMDD'),
       TO_CHAR(SYSDATE - (DBMS_RANDOM.VALUE(1, 86400) / 24 / 60 / 60), 'HH24MISS'),
       TRUNC(DBMS_RANDOM.VALUE(1, 10)),
       TRUNC(DBMS_RANDOM.VALUE(1000, 100000), -3),
       PRDT_CD,
       SYSDATE,
       'DBMSEXPERT'
FROM TB_PRDT,
     (SELECT DUMMY FROM DUAL CONNECT BY LEVEL <= 10);

COMMIT;
  • 제약 조건 생성하기

ALTER TABLE TB_PRDT
    ADD CONSTRAINT TB_PRDT_PK
        PRIMARY KEY (PRDT_CD);

ALTER TABLE TB_PRDT_SALE_DAY
    ADD CONSTRAINT TB_PRDT_SALE_DAY_PK
        PRIMARY KEY (SALE_NO);

ALTER TABLE TB_PRDT_SALE_DAY
    ADD CONSTRAINT TB_PRDT_SALE_DAY_FK
        FOREIGN KEY (PRDT_CD) REFERENCES TB_PRDT (PRDT_CD);
  • 인덱스 생성 및 통계 정보 생성하기

ANALYZE TABLE TB_PRDT COMPUTE STATISTICS
    FOR TABLE FOR ALL INDEXES FOR ALL INDEXED COLUMNS SIZE 254;

ANALYZE TABLE TB_PRDT_SALE_DAY COMPUTE STATISTICS
    FOR TABLE FOR ALL INDEXES FOR ALL INDEXED COLUMNS SIZE 254;

튜닝 전 쿼리

SELECT B.PRDT_CD,
       MIN(B.PRDT_NM),
       SUM(A.SALE_CNT),
       SUM(A.SALE_AMT)
FROM TB_PRDT_SALE_DAY A,
     TB_PRDT B
WHERE A.SALE_DT BETWEEN '20120101' AND '20131231'
  AND A.PRDT_CD = B.PRDT_CD
GROUP BY B.PRDT_CD;

-- [2021-07-29 16:40:24] 89,230 rows retrieved starting from 1 in 4 s 162 ms (execution: 365 ms, fetching: 3 s 797 ms)
  • SQL 분석

    • TB_PRDT_SALE_DAY 테이블과 TB_PRDT 테이블을 조인

    • 조인 방식은 옵티마이저의 선택에 따른다.

  • SQL의 문제점

    • TB_PRDT 테이블과 TB_PRDT_SALE_DAY 테이블은 1:M 관계

    • 1쪽 집합인 TB_PRDT 테이블의 PRDT_CD를 기준으로 GROUP BY하여 집계 결과를 계산한다.

    • SQL은 인라인 뷰를 이용하여 M 쪽의 집합인 TB_PRDT_SALE_DAY 테이블을 먼저 GROUP BY한 후 TB_PRDT 테이블과 1:1 관계를 만든 다음 다음 조인 연산을 수행한다면 최적의 성능을 낼 수 있다.

  • Execute Plan

    • TB_PRDT_SALE_DAY 테이블을 풀 스캔(TABLE ACCESS FULL)한다.

    • TB_PRDT 테이블을 풀 스캔(TABLE ACCESS FULL) 한다.

    • 4번과 5번을 해시 조인(HASH JOIN)한다.

    • GROUP BY 연산을 수행한다.

    • SELECT 절을 연산 수행한다.

튜닝

SELECT /*+ NO_MERGE(A) */
    B.PRDT_CD,
    B.PRDT_NM,
    A.SALE_CNT_SUM,
    A.SALE_AMT_SUM
FROM (
         SELECT A.PRDT_CD,
                SUM(A.SALE_CNT) SALE_CNT_SUM,
                SUM(A.SALE_AMT) SALE_AMT_SUM
         FROM TB_PRDT_SALE_DAY A
         WHERE A.SALE_DT BETWEEN '20120101' AND '20131231'
         GROUP BY A.PRDT_CD
     ) A,
     TB_PRDT B
WHERE A.PRDT_CD = B.PRDT_CD;

-- [2021-07-29 16:42:25] 89,230 rows retrieved starting from 1 in 2 s 930 ms (execution: 331 ms, fetching: 2 s 599 ms)
  • SQL 분석

    • NO_MERGE 힌트를 사용하여 인라인 뷰 A가 메인 쿼리와 같은 레벨로 View Merging되지 않도록 한다.

    • TB_PRDT_SALE_DAY 테이블을 인라인 뷰 내에서 읽는다.

    • PRDT_CD를 기준으로 GROUP BY하여 TB_PRDT 테이블과 조인 연산을 최소화한다.

  • Execute Plan

    • TB_PRDT_SALE_DAY 테이블을 풀 스캔(TABLE ACCESS FULL) 한다.

    • GROUP BY 연산을 수행

    • 4번과 3번의 연산을 인라인 뷰(View)로 구성

    • TB_PRDT 테이블을 테이블 풀 스캔(TABLE ACCESS FULL)한다.

    • 2번과 5번을 해시 조인(HASH JOIN)한다.

    • SELECT 절의 연산을 수행한다.

인라인 뷰를 이용한 해시 조인 성능 극대화 실행 계획
인라인 뷰를 이용한 해시 조인 성능 극대화 튜닝 실행 계획
개발자를 위한 오라클 SQL 튜닝
해시 조인
해시 조인 튜닝