29개 쿼리를 같은 기준으로 분석했습니다
2025년 9월부터 10월까지 본사 10건과 해외법인 19건, 총 29개의 슬로우 쿼리를 개선했습니다. 유난히 느린 SQL 하나를 고르는 작업과는 달랐습니다. 업무 기능과 DB 환경이 다른 쿼리를 같은 기준으로 비교하고, 속도와 결과 정합성을 함께 확인해야 했습니다.
특정 문법이나 힌트를 정답처럼 적용하는 대신 세 가지 원칙을 공통으로 사용했습니다.
- 운영 로그와 사용 빈도를 함께 보고 개선 대상을 선정합니다.
- 조인과 집계 전에 처리할 데이터 범위를 먼저 줄입니다.
- 수행시간뿐 아니라 개선 전후 결과가 같은지도 확인합니다.
개선 자료에 기록된 수행시간을 기준으로 29건 가운데 본사 8건과 해외법인 16건, 총 24건의 개선 후 수행시간을 1초 이내로 줄였습니다.
24건 1초 이내는 쿼리별 개선 자료에 기록된 수행 시간입니다. 장기 운영 SLA나 p95·p99를 뜻하지 않으며, 29건 전체가 1초 이내였다는 의미도 아닙니다.
실행시간과 호출 빈도로 우선순위를 정했습니다
Slow Query 목록에서는 수행시간이 가장 긴 SQL이 먼저 눈에 들어옵니다. 하지만 운영 영향도는 한 번의 수행시간만으로 결정되지 않습니다.
- 한 번 실행될 때는 매우 느리지만 거의 사용되지 않는 쿼리
- 개별 수행 시간은 상대적으로 짧지만 반복 호출되는 쿼리
- 조회 화면뿐 아니라 배치나 후속 처리까지 지연시키는 쿼리
이 세 종류의 우선순위는 같을 수 없습니다. 운영 로그에서 확인한 수행 시간과 실제 사용 빈도를 함께 살펴보고 개선 대상을 정했습니다. 본사와 해외법인을 나누어 목록을 만들고, 각 쿼리의 기능·수행 횟수·평균 수행 시간·개선 전후 수행 시간을 같은 형식으로 기록했습니다.
이 기준으로 운영에서 반복적으로 비용을 만드는 SQL부터 골랐습니다.
쿼리가 달라도 분석 순서는 같았습니다
대상이 많을수록 쿼리마다 기록 방식이 다르면 비교와 재검증이 어려워집니다. 각 쿼리를 다음 형식으로 정리했습니다.
- 기능과 호출 지점
- 변경 전·후 SQL
- 사용 가능한 인덱스
- 개선 전후 실행계획
- 개선 전후 수행시간
- 적용한 개선 방법
Oracle과 MySQL 환경이 섞여 있었지만 분석 순서는 같았습니다. 실행계획에서 많은 행을 읽거나 불필요한 조인·집계를 수행하는 지점을 찾고, SQL 실행 순서를 바꾼 뒤 동일한 기준으로 다시 측정했습니다.
개선 기록에서 반복적으로 나타난 원인은 크게 세 가지였습니다.
- 데이터 범위를 줄이기 전에 여러 테이블을 조인하는 구조
- 기존 인덱스를 충분히 활용하지 못하게 하는 조건
- 필요한 일부 값을 얻기 위해 큰 데이터 집합 전체를 조인하거나 같은 서브쿼리를 반복하는 구조
1. 조인 전에 처리 범위부터 줄였습니다
가장 자주 적용한 방법은 조인 전에 기준 데이터의 범위를 줄이는 것이었습니다.
아래 SQL은 실제 테이블 이름과 업무 조건을 드러내지 않도록 구조만 단순화했습니다.
-- AS-IS 구조
SELECT ...
FROM large_base b
JOIN detail d
ON d.base_id = b.id
JOIN history h
ON h.base_id = b.id
WHERE b.scope_key = :scopeKey
AND b.business_date >= :from
AND b.business_date < :to;이 SQL은 옵티마이저의 판단과 통계 상태에 따라 큰 데이터 집합을 먼저 조인한 뒤 필터링할 수 있습니다. 조인 대상이 늘어나면 이후의 정렬과 집계 비용도 함께 커집니다.
개선 시에는 업무 조건으로 기준 데이터를 먼저 줄인 뒤 필요한 테이블을 조인하도록 실행 순서를 바꿨습니다.
-- TO-BE 개념 구조
WITH target_base AS (
SELECT id,
required_column
FROM large_base
WHERE scope_key = :scopeKey
AND business_date >= :from
AND business_date < :to
)
SELECT ...
FROM target_base b
JOIN detail d
ON d.base_id = b.id
JOIN history h
ON h.base_id = b.id;CTE 자체가 성능을 보장하지는 않습니다. 인라인 처리될지 물리화될지는 DBMS와 실행계획에 따라 달라집니다. 인라인 뷰든 CTE든 여기서 중요한 건 조인 전에 행 수를 줄여, 뒤의 조인·정렬·집계로 넘어가는 데이터 양을 제한하는 일입니다.
2. 새 인덱스보다 기존 조건을 먼저 점검했습니다
인덱스 관련 성능 문제를 만나면 새 인덱스부터 떠올리기 쉽습니다. 하지만 29개 쿼리에 새 인덱스를 일괄 생성하지는 않았습니다. 먼저 기존 인덱스를 활용할 수 있도록 WHERE 절과 JOIN 절의 조건, 복합 인덱스의 선두 컬럼, 업무 범위를 점검했습니다.
- 조회 범위를 결정하는 조건이 빠져 있지 않습니까?
- 복합 인덱스의 컬럼 조합과 SQL 조건이 맞습니까?
- 선택도가 높은 조건을 조인 전에 적용할 수 있습니까?
- 조건식 때문에 인덱스 접근 범위가 불필요하게 넓어지지 않습니까?
다음 역시 실제 SQL이 아닌 개념 예시입니다.
-- 범위를 충분히 좁히지 못하는 조건
WHERE target_id = :targetId
-- 업무 범위까지 함께 제공하는 조건
WHERE scope_key = :scopeKey
AND target_id = :targetId인덱스 사용 여부와 함께 실제로 읽는 범위와 후속 조인으로 전달되는 행 수가 줄었는지 확인했습니다.
3. 일부 값을 얻으려고 전체를 조인하지 않았습니다
일부 쿼리는 화면에 표시할 몇 개의 값을 얻기 위해 큰 테이블을 미리 조인하고 있었습니다. 이런 조인은 기준 행을 늘리고, 중복 제거와 추가 집계를 필요하게 만들 수 있습니다.
이 경우 기준 결과 집합을 먼저 만든 뒤 필요한 값만 선택적으로 조회하도록 바꿨습니다.
WITH core_result AS (
SELECT id,
status
FROM large_base
WHERE ...
)
SELECT c.id,
c.status,
(
SELECT l.display_value
FROM small_lookup l
WHERE l.lookup_key = c.id
) AS display_value
FROM core_result c;스칼라 서브쿼리가 항상 조인보다 빠른 것은 아닙니다. 기준 결과가 큰 상태에서 행마다 서브쿼리를 실행하면 오히려 느려질 수 있습니다. 이 방법은 기준 결과를 먼저 충분히 줄일 수 있고, 부가 값의 조회 범위가 제한적인 경우에만 사용했습니다.
같은 관점에서 불필요한 조인, 중복 서브쿼리, 비효율적인 집계도 함께 정리했습니다. 쿼리마다 데이터가 불필요하게 커지는 지점을 찾아야 했습니다.
수행시간만 줄었다고 완료하지 않았습니다
쿼리 튜닝에서 가장 위험한 결과는 “빨라졌지만 다른 데이터를 반환하는 SQL”입니다. 업무 시스템의 SQL에는 상태값, 기간, 회사 범위, 예외 조건처럼 성능만 보고 제거할 수 없는 규칙이 포함돼 있기 때문입니다.
개선 전후에 다음을 확인했습니다.
- 변경 전·후 SQL의 실행계획 비교
- 동일 조건에서 실행시간 비교
- 결과 건수 비교
- 반환 값 비교
당시 개선 전후 SQL을 직접 실행해 결과 건수와 값을 대조했습니다. 검증은 자동 회귀 테스트가 아니라 수동 결과 비교로 수행했습니다.
결과
| 구분 | 개선 대상 | 개선 후 수행시간 1초 이내 |
|---|---|---|
| 본사 | 10건 | 8건 |
| 해외법인 | 19건 | 16건 |
| 합계 | 29건 | 24건 |
24건 1초 이내는 개선 자료에 기록된 쿼리별 개선 후 수행 시간을 기준으로 한 결과입니다. 장기간 운영 모니터링에서 측정한 백분위 응답 시간이나 모든 파라미터에서 보장되는 SLA를 뜻하지 않습니다.
일괄 개선에서 정리한 튜닝 기준
실행계획에서 처리 행 수의 변화를 봅니다
실행계획은 반드시 확인해야 하지만 계획의 각 노드를 읽는 데서 끝나면 안 됩니다. 어느 단계에서 데이터가 커지고, 그 데이터가 다음 조인·집계·정렬로 얼마나 전달되는지 함께 봐야 합니다.
인덱스 사용 여부보다 읽는 범위를 봅니다
실행계획에 인덱스가 표시돼도 넓은 범위를 읽으면 충분한 개선이 아닙니다. 조건과 인덱스 컬럼의 관계, 필터 적용 시점, 실제 처리 행 수를 함께 확인해야 합니다.
성능과 정합성을 하나의 완료 조건으로 둡니다
수행시간이 줄어도 결과가 달라지면 개선이 아닙니다. 반대로 결과만 같고 운영 병목이 그대로라면 목표를 달성하지 못한 것입니다. 개선 전후 수행시간과 결과를 함께 비교해야 튜닝을 완료할 수 있습니다.
여러 건을 다룰수록 기록 형식이 중요합니다
변경 전·후 SQL, 실행계획, 수행 시간, 개선 방법을 같은 형식으로 남기면 쿼리별 판단 근거를 다시 확인할 수 있습니다. 개별 튜닝 경험을 다음 작업에서 재사용할 수 있는 기준으로 바꾸는 데도 도움이 됩니다.
마무리
결과는 본사 10건과 해외법인 19건, 총 29개 쿼리 개선이었고 이 가운데 24건은 기록된 수행 시간이 1초 이내로 줄었습니다.
여기서 재사용할 만한 것은 특정 SQL 문법이 아니라 분석 순서입니다. 운영 로그와 사용 빈도로 대상을 고르고, 조인 전에 처리 범위를 줄인 뒤 기존 인덱스를 활용할 조건을 점검했습니다. 마지막에는 개선 전후 결과까지 대조했습니다.
SQL 튜닝은 빠른 쿼리를 만드는 작업이면서 동시에 기존 업무 규칙을 보존하는 작업입니다. 여러 시스템의 쿼리를 다룰수록 이 두 조건을 같은 비중으로 관리해야 합니다.