성능 개선 · Oracle SQL

주문 조건보다 먼저 실행된 GROUP BY가 만든 고정 지연

하위 데이터 전체를 먼저 집계하던 쿼리를 조회 대상 주문만 계산하도록 바꿨습니다. 당시 운영 중 사용자가 확인한 결과, 목록 조회 쿼리는 약 40~50초에서 약 0.5초로 줄었습니다.

OracleSQL 튜닝MyBatis실행계획

조회 범위를 줄여도 응답 시간은 그대로였습니다

Oracle 11g과 MyBatis를 사용하는 주문조회 화면에서 검색 기간을 하루 단위로 줄여도 응답시간이 거의 줄지 않았습니다. 화면 조건은 충분히 좁았지만 핵심 쿼리는 약 40~50초가 걸렸습니다. 조건 변화가 실행 시간에 영향을 주지 않는다는 점에서 화면 렌더링이나 네트워크보다 SQL 내부의 고정 비용을 먼저 의심했습니다.

실행 환경과 전제

  • 기존 반환 컬럼과 화면의 조회 결과를 유지해야 했습니다.
  • 주문 한 건에는 여러 하위 결과가 연결되고 화면에는 주문별 결과 건수가 필요했습니다.
  • 개선 효과는 현업이 반복 사용하는 기간·업체·상태 조건에서 검증해야 했습니다.
  • 실제 테이블·컬럼·인덱스 이름과 업무 데이터는 공개하지 않습니다.

주문 조건보다 하위 데이터 집계가 먼저였습니다

기존 쿼리는 하위 결과 전체를 주문 단위로 먼저 집계한 뒤 주문 목록과 결합했습니다. 주문 검색 조건은 바깥 쿼리에 있었으므로 사용자가 하루치만 조회해도 하위 집계는 전체 범위에서 수행됐습니다. 실행계획에서 먼저 처리되는 집계의 입력 범위가 줄지 않는 것이 고정 비용의 원인이었습니다.

SELECT ...
FROM ORDER_ITEM item
JOIN (
  SELECT BUSINESS_KEY, COUNT(*)
  FROM ORDER_RESULT
  GROUP BY BUSINESS_KEY
) result_count
  ON result_count.BUSINESS_KEY = item.BUSINESS_KEY
WHERE item.REQUESTED_AT >= :fromDate;

선택한 해결 방법

먼저 조회 대상 주문을 줄이고, 남은 주문에 필요한 결과만 계산하도록 상관 서브쿼리 형태로 바꿨습니다. 전체 선집계를 그대로 둔 채 인덱스만 추가할 수도 있지만, 집계에 들어가는 행 수가 그대로라면 개선 폭도 제한적이라고 봤습니다.

아래 코드는 핵심 동작만 보여 주도록 실제 클래스·테이블·설정 이름을 바꾼 예시입니다.

SELECT item.BUSINESS_KEY,
       (SELECT COUNT(*)
          FROM ORDER_RESULT result
         WHERE result.BUSINESS_KEY = item.BUSINESS_KEY) AS RESULT_COUNT
FROM ORDER_ITEM item
WHERE item.REQUESTED_AT >= :fromDate;

상세 조회에서는 기존 인덱스의 선두 조건과 실제 검색 조건이 맞지 않는 구간을 따로 분리했습니다. 이 범위에서는 주문 관련 테이블의 `(법인 키, 업무 키)` 복합 인덱스 필요성을 도출하고 실행계획을 보완했으며, 인덱스 자체는 배포하지 않았습니다.

검증

  • 기존 쿼리와 개선 쿼리의 결과 건수·반환 값을 조건별로 비교했습니다.
  • 기간, 업체, 주문 상태 등 실제 사용 빈도가 높은 조건으로 반복 측정했습니다.
  • 목록과 상세 조회에서 조건별 결과와 실행 시간을 비교하고, 발견한 결함을 보완했습니다.
  • 목록 쿼리 변경만 운영에 반영했습니다. 상세 쿼리는 복합 인덱스 필요성 도출과 실행계획 보완까지 진행했으며 인덱스는 배포하지 않았습니다.

운영 반영 결과와 측정 범위

당시 운영 확인에서 핵심 쿼리 응답 시간은 약 40~50초에서 약 0.5초로 줄었고, 전체 조회의 95% 이상이 1초 이내에 처리됐습니다.

적용할 때 주의할 점

상관 서브쿼리가 언제나 더 빠른 것은 아닙니다. 바깥 결과가 크거나 상관 조건을 받쳐 주는 인덱스가 없다면 반복 접근 비용이 오히려 커집니다. 여기서는 먼저 걸러진 주문 수가 충분히 작고 업무 키로 하위 결과를 찾을 수 있어 주문별 계산이 유리했습니다. 선집계와 주문별 계산 중 어느 쪽이 맞는지는 실제 카디널리티와 조회 패턴으로 결정해야 합니다.