업무시간에 작성하는 조회 속도 400배 늘린방법

Kyungmin·2025년 7월 11일
post-thumbnail

쿼리를 실행했는데 2분이 걸린다고?!?

👨 : 이거 조회가 안되요! 시스템 오류인건가요?
나 : 2분 기다리면 나올거예요 ㅎㅎㅎ

프로젝트를 진행하며 어느때와 같이 필요한 쿼리를 작성하고 있었다. 근데 실행시간이 너무 오래걸리는 쿼리가 있었다.

얼마나 오래걸렸냐고 물어보신다면 무려 111437ms ( 1분 51초) 가 걸리는 .. 쿼리를 작성했다.

만약 사용자에게 조회하는데 2분만 기다려달라고 한다면

.., 접속을 안하고 말 것 같다.

프로젝트 마감 전까지 조회시간을 단축시키는 방법을 찾고 동시에 최선의 방법을 찾는 것이 중요하다고 생각했다.
.
.

아래는 해당 조회 쿼리이다.

기존 쿼리 예시

WITH CTE_A AS (
             ... 
        ) , 
 CTE_B AS (  ... ) ,
 CTE_C AS (  ... ) 
        SELECT
           ...
        FROM WISH_HOME WH
        LEFT JOIN PLEASE_HOME PH
            ON WH.A = PH.B
        WHERE 1=1
            AND ...

약 350 줄 정도의 쿼리였는데 with 절로 상당히 많은 CTE 로 구성되어 있었고 그 안에서는 조인이 대량으로 이루어지고 있었다.

수정 전 실행시간 확인

111237 ms ( 1분 51 초 )

이 조회쿼리의 실행시간을 어떻게 단축할 수 있을까?

선택

  1. Index
  2. 쿼리 힌트(Query Hint)

고민해본 것은 크게 2가지였다.
구글링 해보면 쿼리 튜닝 시 가장 많이 사용하고, 나또한 익숙한 인덱스를 사용과 쿼리힌트를 사용하는 방법이었다.

선택을 하기 전 고려해야할 점들이 있었다.

Index vs. Query Hint, 결정을 내리기 전에 체크해야 할 것

  1. 회사 공용 테이블들에 인덱스가 걸려있지 않다.
  2. 서버 메모리 여유가 충분
  3. 즉각적인 응답 속도 개선 필요

특히 대량 쓰기(INSERT·UPDATE)가 잦은 테이블이기 때문에, 인덱스 추가 시 이 부분도 고려했어야 했다.

판단 근거현재 쿼리·환경 특성대안
① 회사 공용 테이블 인덱스 부재000 테이블(TB_A, TB_B 등)에 조인 키 인덱스 없음  • 인덱스 없는 대용량 테이블은 Nested Loop → M × N 반복 스캔
 • Hash Join은 인덱스 의존도가 0 이므로 최적의 대안
② CTE 단계에서 행 수 대폭 축소CONTRACT_BASE·LEDGER_NETBIZ·CROSS1 등 CTE에서 회계연도 필터 + GROUP BY로 이미 행 수가 수천 수준으로 줄어 있음  • FROM 순서를 그대로 유지해야 “작아진 결과 ➜ 큰 테이블” 흐름 유지
 • FORCE ORDER가 이 순서를 강제해 옵티마이저의 잘못된 재배치를 차단

그렇다면 다음으로는 쿼리 힌트를 고려해보았다.

나의 현재 쿼리는 많은 테이블이 조인되고 있었고, 각 테이블에 인덱스 또한 없었다. 때문에 NL 조인은 나의 상황과 맞지 않았다.

SQL Server (MSSQL) 쿼리 힌트(Query Hint)

OPTION (…) 절에 넣어 SQL Server 옵티마이저의 기본 계획 선택을 강제로 바꾸는 지시문이다. 힌트를 쓰면 옵티마이저가 평가하는 모든 연산자·조인 방식·메모리 사용량 등에 직접 영향이 간다.
Oracle / PostgreSQL처럼 /+ … / 와 같이 주석으로 힌트를 쓰지 않는다. 주석은 SQL Server에겐 단순 주석일 뿐이라서 효과가 없다.

계층예시작성 위치특징
Query HintOPTION (HASH JOIN, FORCE ORDER) SELECT … FROM.. 끝부분쿼리 전체에 적용, 여러 개를 ,로 연결
Join HintINNER HASH JOIN, INNER LOOP JOINFROM … JOIN 키워드 앞두 테이블 간 조인 알고리즘 강제, 동시에 조인 순서도 고정 
Table HintFROM T WITH (NOLOCK, INDEX(idx1))테이블(별칭) 뒤잠금·스캔 방식·특정 인덱스 고정 등
USE HINTOPTION (USE HINT('QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_150'))OPTION 절SQL 2016 SP1+ 전용, DMV로 지원 목록 확인 가능 

쿼리 힌트는 이번에 해당 고민을 하면서 처음 알게된 내용이었다.

쿼리 힌트 - 해시조인과 Force Order 를 SELECT 마지막에 작성하게되면 해시조인을 강제로 나의 알고리즘에 적용하게 되고, 옵티마이저의 조인 순서 재배치를 금지하여 강제로 순서를 지정한다.

FORCE ORDER 를 통해 조인 순서를 강제한다. 나는 이미 WITH 절로 조인 순서를 직접 설계해둔 상태였고, 이를 FORCE ORDER 로 강제함으로 옵티마이저가 잘못된 조인을 하게되는 상황을 막는다.

HASH JOIN 은 조인 대상 중 하나를 메모리에 해시 테이블로 올리고, 다른 쪽을 한 번만 스캔하면서 조인한다.

NL 방식

  • A 테이블에 이름 10개
  • B 테이블에 이름과 연결된 번호가 100만개
    100만 X 10 를 수행 (최악의 경우)

HASH JOIN

  • A 테이블에 이름 10개
  • B 테이블에 이름과 연결된 번호가 100만개
    100만 + 10 = 약 1,000,010 회 탐색 수행

수정 후 실행시간 확인 - 433.61 배 ( 99.77 % 속도 향상 )

  • 쿼리 힌트 OPTION (HASH JOIN, FORCE ORDER) 적용

    257 ms ( 0.25초 )

OPTION (..) 사용은 단기적인 관점에서 조회속도만 생각했을 땐 좋은 방법이었다.
하지만 장기적으로 보았을 때 이는 쿼리 유지보수 측면도 힘들어지고 옵티마이저의 결정권을 빼았기 때문에 나중에 변화 대응에 힘들어질 수 있다.

때문에 장기적으로 인덱스 설계를 통해 힌트 없이도 좋은 성능이 나오는 구조로 리펙토링 하는 작업이 필요할 것 이다.


참고

  1. SQL JOIN - HASH JOIN
  2. [친절한 SQL 튜닝] NL 조인, 소트 머지 조인, 해시 조인

0개의 댓글