DB·SQL 실무 가이드 · Part 12

통계정보와 옵티마이저 관리

옵티마이저의 판단 근거를 관리해 계획이 흔들리지 않게 하기

작성 기준2026년 7월버전과 지원 현황은 이후 달라질 수 있으니 공식 문서를 함께 확인하세요.

이 파트에서 다루는 내용

통계의 구조수집 방법히스토그램계획이 바뀌는 이유배치 후 통계
01

옵티마이저의 예상은 통계에서 나옵니다

Part 7에서 예상 건수와 실제 건수가 벌어지면 계획 전체가 잘못된다고 했습니다. 그 예상의 근거가 통계정보입니다.

통계가 현실과 다르면 옵티마이저는 성실하게 잘못된 결론을 냅니다. 쿼리도 인덱스도 멀쩡한데 느린 경우, 상당수가 여기에 원인이 있습니다.

테이블 통계
규모

행 수, 사용 블록 수, 평균 행 길이. 전체 스캔 비용을 계산하는 기준이 됩니다.

컬럼 통계
분포

고유값 개수, 최소·최대값, NULL 개수. 조건에 몇 건이 걸릴지 추정하는 데 쓰입니다.

인덱스 통계
접근 비용

인덱스 높이, 리프 블록 수, 클러스터링 팩터. 인덱스를 탈지 말지 판단하는 근거입니다.

히스토그램
선택 사항

값이 고르게 퍼져 있지 않을 때 실제 분포를 담습니다. 다음 섹션에서 따로 다룹니다.

통계가 언제 수집됐는지 확인sql
SELECT table_name,
       num_rows,
       blocks,
       last_analyzed,
       ROUND(SYSDATE - last_analyzed, 1) AS days_ago
FROM   user_tables
ORDER BY last_analyzed NULLS FIRST;

-- 실제 건수와 비교해 봅니다
SELECT COUNT(*) FROM orders;

last_analyzed 가 NULL이면 통계가 아예 없다는 뜻입니다. num_rows 와 실제 COUNT 가 크게 다르면 통계가 낡은 것입니다. 느린 쿼리를 만났을 때 가장 먼저 볼 두 가지입니다.

02

통계는 DBMS_STATS로 수집합니다

예전에는 ANALYZE 명령을 썼지만 지금은 쓰지 않습니다. DBMS_STATS 패키지가 표준이고, ANALYZE 는 일부 통계를 옵티마이저가 쓰지 않는 형태로 수집합니다.

Oracle은 유지관리 시간대에 자동으로 통계를 수집합니다. 대부분의 경우 이걸로 충분하지만, 대량 변경 직후처럼 다음 자동 수집을 기다릴 수 없을 때는 직접 수집합니다.

  • `cascade => TRUE` 를 빠뜨리면 인덱스 통계가 낡은 채로 남습니다.
  • 운영 시간에 큰 테이블을 수집하면 부하가 걸립니다. 작업 시간대를 잡거나 병렬도를 낮춥니다.
  • 통계 수집 자체가 계획을 바꿉니다. 수집 후 주요 쿼리의 응답시간을 확인하는 절차를 함께 둡니다.
  • 자동 수집 작업이 도는 시간대는 `DBA_AUTOTASK_WINDOW_CLIENTS` 등에서 확인할 수 있습니다.
테이블 통계 수집sql
BEGIN
  DBMS_STATS.GATHER_TABLE_STATS(
    ownname          => USER,
    tabname          => 'ORDERS',
    estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
    method_opt       => 'FOR ALL COLUMNS SIZE AUTO',
    cascade          => TRUE,        -- 인덱스 통계도 함께
    degree           => 4            -- 병렬로 빠르게
  );
END;
/

AUTO_SAMPLE_SIZE 를 쓰면 Oracle이 표본 크기를 알아서 정합니다. 예전처럼 퍼센트를 직접 지정하는 것보다 정확하고 빠릅니다. degree 는 서버 부하를 봐 가며 정합니다.

03

히스토그램은 값이 몰려 있을 때 필요합니다

히스토그램이 없으면 옵티마이저는 값이 고르게 퍼져 있다고 가정합니다. 상태코드가 세 종류면 각각 3분의 1이라고 봅니다.

그런데 실제로는 DONE이 98%, CANCEL이 1%, READY가 1%인 경우가 많습니다. 이때 `status_cd = 'READY'` 조회는 실제로 1%만 걸리는데 옵티마이저는 33%로 예상해 전체 스캔을 고릅니다.

히스토그램이 있으면 실제 분포를 보고 판단합니다. 값이 한쪽으로 몰린 컬럼에 조건이 자주 걸린다면 검토할 대상입니다.

분포 확인과 히스토그램 수집sql
-- 실제 분포를 먼저 봅니다
SELECT status_cd, COUNT(*),
       ROUND(RATIO_TO_REPORT(COUNT(*)) OVER () * 100, 2) AS pct
FROM   orders
GROUP BY status_cd
ORDER BY 2 DESC;

-- 특정 컬럼에만 히스토그램 수집
BEGIN
  DBMS_STATS.GATHER_TABLE_STATS(
    ownname    => USER,
    tabname    => 'ORDERS',
    method_opt => 'FOR COLUMNS SIZE 254 STATUS_CD'
  );
END;
/

-- 히스토그램이 생겼는지 확인
SELECT column_name, num_distinct, histogram, num_buckets
FROM   user_tab_col_statistics
WHERE  table_name = 'ORDERS';
히스토그램과 바인드 변수의 충돌

Part 9에서 바인드 변수를 쓰라고 했는데, 바인딩하면 옵티마이저가 값을 모르는 채로 계획을 세워야 합니다. Oracle은 첫 실행 시 값을 엿보고(peeking) 계획을 만드는데, 그 첫 값이 흔한 값이냐 드문 값이냐에 따라 이후 모든 실행의 계획이 결정됩니다. 값이 심하게 몰린 컬럼은 바인딩보다 상수가 나을 수 있고, 이 판단이 필요한 컬럼은 많지 않으니 예외로 관리합니다.

04

어제까지 잘 돌던 쿼리가 오늘 느려집니다

운영에서 가장 당황스러운 상황입니다. 코드를 바꾸지 않았는데 특정 쿼리만 갑자기 느려집니다.

원인은 대개 세 가지입니다. 통계가 새로 수집돼 계획이 바뀌었거나, 바인드 값이 달라져 다른 계획이 선택됐거나, 데이터가 늘어 기존 계획이 더 이상 맞지 않게 된 것입니다.

계획 고정
SQL Plan Baseline

좋은 계획을 등록해 두고 그것만 쓰게 만드는 기능입니다. 계획이 흔들리는 핵심 쿼리에 유효하지만 Enterprise Edition 기능입니다.

힌트로 고정
차선책

Part 7에서 다룬 대로 마지막 수단입니다. 데이터 분포가 바뀌면 그 힌트가 발목을 잡습니다.

근본 대응
권장

통계를 현실에 맞게 유지하고, 예상이 빗나가는 조건에는 히스토그램이나 확장 통계를 검토합니다.

같은 SQL이 여러 계획을 갖고 있는지 확인sql
SELECT sql_id,
       child_number,
       plan_hash_value,
       executions,
       ROUND(elapsed_time / GREATEST(executions, 1) / 1000, 1) AS avg_ms,
       buffer_gets,
       first_load_time
FROM   v$sql
WHERE  sql_id = :sql_id
ORDER BY child_number;

plan_hash_value 가 서로 다르면 같은 SQL에 여러 계획이 존재한다는 뜻입니다. 평균 시간이 크게 차이 나는 계획이 있으면 그 사이에 무엇이 바뀌었는지 추적합니다.

통계를 이전 시점으로 되돌리기sql
-- 통계 이력이 얼마나 보관되는지 (기본 31일)
SELECT DBMS_STATS.GET_STATS_HISTORY_RETENTION FROM DUAL;

-- 이 테이블의 통계가 언제 바뀌었는지
SELECT table_name, stats_update_time
FROM   user_tab_stats_history
WHERE  table_name = 'ORDERS'
ORDER BY stats_update_time DESC;

-- 문제가 생기기 전 시점으로 복원
BEGIN
  DBMS_STATS.RESTORE_TABLE_STATS(
    ownname   => USER,
    tabname   => 'ORDERS',
    as_of_timestamp => SYSTIMESTAMP - 2
  );
END;
/

통계 수집 후 성능이 무너졌을 때 쓸 수 있는 응급 처치입니다. 다만 근본 원인을 덮는 것이므로, 되돌려 급한 불을 끈 뒤 왜 새 통계에서 계획이 나빠졌는지 확인해야 합니다.

05

배치 뒤에는 통계를 다시 수집합니다

야간 배치로 수백만 건을 적재했는데 통계가 어제 것 그대로면, 아침 첫 조회부터 잘못된 계획으로 돕니다. 자동 수집을 기다리면 그날 하루를 잃습니다.

배치의 마지막 단계에 통계 수집을 넣는 것이 기본입니다.

작업용 임시 테이블
특수 케이스

비었다가 채워지길 반복하는 테이블은 통계 시점에 따라 계획이 요동칩니다. 대표적인 크기로 통계를 만들어 고정하거나, 통계를 지우고 동적 샘플링에 맡기는 방법이 있습니다.

통계 잠금
LOCK_TABLE_STATS

의도한 통계가 자동 수집으로 덮이지 않게 잠글 수 있습니다. 잠갔다는 사실을 문서에 남기지 않으면 나중에 원인 모를 문제가 됩니다.

파티션 테이블
증분 수집

파티션 단위로 수집하고 전체 통계를 증분으로 갱신하면 시간이 크게 줄어듭니다. 파티션은 Part 13에서 다룹니다.

운영 원칙

통계는 자동 수집에 맡기되, 대량 변경 직후에는 직접 수집합니다. 수집 후에는 주요 쿼리의 응답시간을 확인합니다. 계획이 무너졌을 때 되돌릴 수 있도록 통계 이력 보관 기간을 알고 있어야 합니다.

체크

이 파트 완료 기준