Oracle 환경에서 대용량 MERGE 문을 실행할 때, 성능 저하 및 Snapshot too old 오류가 발생하는 사례는 종종 존재한다. 본 글에서는 실무 중 경험한 실제 사례를 바탕으로, 문제 상황 분석과 해결 방안을 기술하며, MERGE 시 주의해야 할 뷰 사용 패턴에 대해서도 정리한다.
문제 상황
운영 배치에서 아래와 같은 형태의 MERGE 문을 사용 중이었다.
MERGE INTO A
USING (
SELECT ID, ID2, COUNT(*) AS CNT
FROM B
GROUP BY ID, ID2
) B
ON (A.ID = B.ID AND A.ID2 = B.ID2)
WHEN MATCHED THEN
UPDATE SET A.CNT = B.CNT
WHERE A.DATE = TO_DATE('2025-04-17', 'YYYY-MM-DD');
주요 증상
- A.DATE 컬럼에 인덱스가 존재함에도 불구하고 FULL TABLE SCAN 발생
- 단순 SELECT 쿼리에서는 인덱스를 정상적으로 탐
- 위 쿼리 실행 시 성능이 급격히 저하되며, 일부 경우 Snapshot too old 오류 발생
원인 분석
- MERGE는 내부적으로 대상 테이블(A)과 소스 테이블(B)을 같은 레벨에서 조인하는 구조로 처리됨.
- A.TYPE 조건이 WHERE 절에 존재하지만, 옵티마이저가 해당 조건을 조인 이후에 적용할 수 있음.
- 그 결과, 인덱스 사용이 불가능해지고 A 테이블에 대해 FULL SCAN이 발생함.
해결 방안
1. INLINE VIEW를 통한 사전 필터링
A 테이블에 대해 WHERE 조건을 사전에 적용하여 인라인 뷰로 감싸면, 옵티마이저가 조건을 먼저 적용할 수 있다. 이로 인해 인덱스 사용이 가능해지고 성능이 개선된다.
MERGE INTO (
SELECT * FROM A WHERE TYPE = TO_DATE('2025-04-17', 'YYYY-MM-DD')
) A
USING (
SELECT ID, ID2, COUNT(*) CNT FROM B GROUP BY ID, ID2
) B
ON (A.ID = B.ID AND A.ID2 = B.ID2)
WHEN MATCHED THEN
UPDATE SET A.CNT = B.CNT;
기대 효과
- 옵티마이저가 TYPE 조건을 사전 적용함으로써 인덱스 활용 가능
- 조인 대상 데이터가 줄어들어 성능 개선
- Undo 영역 소모가 줄어들어 Snapshot too old 오류 감소
2. MERGE 문을 UPDATE 방식으로 변경
MERGE 문을 사용하지 않고 UPDATE 문으로 변경하여 수행하는 것도 성능 개선에 효과적이다.
UPDATE (
SELECT A.CNT, B.CNT AS NEW_CNT
FROM A
JOIN (
SELECT ID, ID2, COUNT(*) CNT FROM B GROUP BY ID, ID2
) B ON A.ID = B.ID AND A.ID2 = B.ID2
WHERE A.TYPE = TO_DATE('2025-04-17', 'YYYY-MM-DD')
)
SET CNT = NEW_CNT;
기대 효과
- UPDATE 대상 범위를 명확히 지정 가능
- 옵티마이저가 인덱스를 더 쉽게 활용할 수 있음
- 실행 계획이 단순하고 예측 가능
MERGE 시 피해야 할 VIEW 패턴
INLINE VIEW를 사용할 경우, Oracle은 해당 뷰가 "업데이트 가능한 뷰(updatable view)"여야만 MERGE 또는 UPDATE가 가능하다. 다음과 같은 뷰 형태는 UPDATE 불가능하거나 성능 저하를 유발할 수 있으므로 주의해야 한다.
패턴 | 이유 |
SELECT * FROM A JOIN B | 다중 테이블 조합으로 인해 UPDATE 대상이 모호해짐 |
SELECT a.*, b.col FROM ... | 뷰 내에 다른 테이블 컬럼이 포함됨 |
SELECT DISTINCT ... | 집계 연산으로 인해 원본 행 추적이 어려움 |
SELECT col1, col2, UPPER(col3) | 표현식이 포함되면 컬럼 원본 식별 불가 |
SELECT ... FROM A WHERE ... ORDER BY col | 정렬 포함된 뷰는 업데이트 불가능 |
가능한 한 단순한 구조의 뷰를 사용하는 것이 좋다:
SELECT * FROM A WHERE TYPE = TO_DATE(...)
성능 개선 결과
항목 | 개선 전 | 개선 후 |
실행 시간 | 수 분 이상 | 수 초 이내 |
인덱스 사용 | 불가 | 가능 (INDEX RANGE SCAN) |
오류 발생 | Snapshot too old 발생 | 오류 없음 |
실행 계획 | 복잡함 | 단순, 안정적 |
결론
Oracle에서 MERGE를 사용할 때 성능 이슈가 발생한다면, 옵티마이저가 WHERE 조건을 적절히 적용하지 못하는 구조일 가능성을 우선적으로 검토해야 한다. 특히 인덱스를 사용해야 하는 조건이 있음에도 불구하고 FULL SCAN이 발생하는 경우, 인라인 뷰를 활용하여 WHERE 조건을 사전에 필터링하거나, UPDATE 방식으로 쿼리를 재구성하는 것이 좋은 해결 방법이 될 수 있다.
실제 환경에서는 이러한 단순한 구조 변경만으로도 수십 배의 성능 개선과 함께 오류 방지 효과를 기대할 수 있다.
'Programming > DB' 카테고리의 다른 글
ROWNUM vs ROW_NUMBER() — 차이점 및 상세 비교 정리 (0) | 2025.04.24 |
---|---|
Oracle PL/SQL 프로시저 작성법: 기본 개념과 실습 예제 (0) | 2025.04.18 |
SQL에서 IN, NOT IN vs EXISTS, NOT EXISTS 차이 — 그리고 NULL에 대한 함정 (0) | 2025.04.17 |
Oracle DB에서 Redo 로그, Undo, 아카이브 로그의 개념 정리 (0) | 2025.04.15 |
RDB에서 페이징 쿼리의 중요성과 LIMIT, OFFSET 방식의 장단점 (0) | 2025.04.13 |