튜닝

오라클 튜닝 기초 07강 — SQL 모범사례 & 대기 이벤트 첫걸음

🎯 학습 목표

  • 바인드 변수를 써야 하는 이유(공유 풀·하드파싱)를 이해한다.
  • 부분범위 처리 등 응답시간을 줄이는 작성 습관을 익힌다.
  • 대기 이벤트로 “무엇이 시간을 먹는지” 보는 첫걸음을 뗀다.

📖 1) 바인드 변수를 써라

오라클은 SQL을 실행하기 전에 파싱(parse)합니다. 처음 보는 SQL은 실행계획을 새로 만드는 하드 파싱(hard parse)을 하고, 그 결과를 공유 풀(Shared Pool)에 캐시해 둡니다. 같은 SQL이 또 오면 캐시를 재사용(소프트 파싱)해 빠릅니다. 그런데 값을 문자열로 박아 넣으면 매번 “다른 SQL”로 취급돼 하드 파싱이 폭증합니다.

-- ❌ 리터럴: 값마다 다른 SQL → 매번 하드 파싱, 공유 풀 낭비
SELECT * FROM emp WHERE empno = 7369;
SELECT * FROM emp WHERE empno = 7499;   -- 옵티마이저 입장에선 완전 다른 SQL

-- ✅ 바인드 변수: 계획을 재사용 → 파싱 부하↓, 캐시 효율↑
SELECT * FROM emp WHERE empno = :empno;
// 애플리케이션(JDBC)에서도 PreparedStatement로 바인딩
PreparedStatement ps = conn.prepareStatement(
    "SELECT * FROM emp WHERE empno = ?");
ps.setInt(1, 7369);   // 값은 바인딩으로 — 문자열 연결(+)로 SQL 만들지 말 것

바인드 변수는 성능뿐 아니라 SQL 인젝션 방어에도 핵심입니다. 문자열을 직접 이어 붙여 SQL을 만들지 마세요.

📖 2) 필요한 만큼만 — SELECT * 줄이기

-- ❌ 필요 없는 컬럼까지 다 읽음
SELECT * FROM orders WHERE customer_id = :id;

-- ✅ 쓰는 컬럼만. 운 좋으면 인덱스만 읽고 끝낼 수도(커버링 인덱스)
SELECT order_id, order_date, amount
  FROM orders WHERE customer_id = :id;

조회 컬럼이 모두 인덱스에 포함되면 테이블을 안 읽고 인덱스만으로 끝낼 수 있습니다(커버링 인덱스 효과). SELECT *는 이 기회를 막습니다.

📖 3) 부분범위 처리 — 첫 화면을 빨리

화면에 20건만 보여주는데 1만 건을 다 정렬해 만들 필요는 없습니다. 필요한 만큼만 만들고 멈추는 것이 부분범위 처리입니다. 페이징에는 12c 이상의 표준 구문을 권장합니다.

-- 12c+ : 정렬된 상위 N건만 (인덱스로 정렬을 대체하면 매우 빠름)
SELECT order_id, order_date
  FROM orders
 ORDER BY order_date DESC
 OFFSET 0 ROWS FETCH FIRST 20 ROWS ONLY;

📖 4) 집합으로 생각하기 — 루프 대신 한 방에

❌ 애플리케이션에서 1만 번 루프 돌며 1건씩 UPDATE (네트워크·파싱 왕복 폭발)
✅ 하나의 SQL(또는 MERGE)로 집합 단위 처리, 대량은 배열 처리(bulk) 활용
   "행 하나하나 절차적으로"가 아니라 "집합을 한 번에"가 DB의 사고방식

📖 5) 대기 이벤트(Wait Event) 첫걸음

SQL이 느릴 때, 시간이 어디서 새는지 알려주는 것이 대기 이벤트입니다. 세션은 CPU를 쓰거나, 무언가를 기다립니다(디스크 읽기, 락 등). 무엇을 기다렸는지 보면 원인이 보입니다.

db file sequential read : 단일 블록 읽기 대기 → 보통 '인덱스 경유 테이블 접근'.
                          많으면 인덱스로 너무 많은 행을 들락거리는지 의심
db file scattered read  : 다중 블록 읽기 대기 → 보통 'Full Table Scan'.
                          좁은 검색인데 이게 많으면 인덱스 미사용 의심
log file sync           : 커밋 시 redo 기록 대기 → 너무 잦은 commit 의심
enq: TX - row lock      : 다른 트랜잭션의 락 대기 → 동시성/락 경합 문제
-- 지금 세션들이 무엇을 기다리는지(활성 세션) 살펴보기
SELECT sid, event, state, seconds_in_wait
  FROM v$session
 WHERE status = 'ACTIVE' AND wait_class <> 'Idle';

대기 이벤트 분석은 깊은 주제이지만(AWR/ASH 리포트 등), “느림 = 무언가를 기다림”이고 그 무엇을 이름으로 알 수 있다는 사고방식만 잡아도 큰 진전입니다.

⚠️ 주의사항

  • 바인드 변수가 항상 만능은 아닙니다. 값 분포가 극단적으로 치우친 컬럼(히스토그램 있는 컬럼)에서는 바인드된 첫 값으로 만든 계획이 다른 값에 안 맞는 바인드 변수 핍킹(peeking) 문제가 있을 수 있습니다(중급 주제).
  • 너무 잦은 COMMIT은 log file sync 대기를 키웁니다. 반대로 너무 긴 트랜잭션은 락·UNDO를 키웁니다. 적절한 단위로 커밋하세요.

🧭 마무리 & 다음 단계

  • 이 강좌의 한 문장 요약: “실행계획을 보고(2강), 적은 일량으로 읽게 인덱스·조인을 바로잡고(3·4·5강), 옵티마이저가 옳은 판단을 하도록 통계를 관리하라(6강).”
  • 더 깊게: 실행계획 고정(SQL Plan Baseline), AWR/ASH로 대기 분석, 파티셔닝, 병렬 처리, 옵티마이저 힌트 심화.
  • 관련 강좌: Oracle 카테고리의 다른 글과 함께 보면 좋습니다.