LabHub

SI DB 운영 · 조회 튜닝과 실행계획 · 실습

실행계획 읽고 느린 쿼리 개선하기

LabHub 에서 이어서 보기

목표

실행계획을 읽고, 인덱스 유무·컬럼 순서·함수 조건·커버링 여부가 계획을 어떻게
바꾸는지 직접 확인하며, N+1 을 조인으로 개선하고 튜닝 리포트를 남길 수 있게 됩니다.

왜 중요한가

실행계획을 읽는다는 것은 노드 이름을 훑는 일이 아니라 **옵티마이저의 예측과
실제 결과의 괴리를 찾는 일**입니다. 그리고 cost 는 시간이 아니라 상대 점수라서
"cost 가 10000 이 넘으면 위험하다" 같은 기준은 근거가 없습니다.
또 하나 중요한 태도 — 전체 스캔이 항상 나쁜 것이 아닙니다. 대부분의 행을
읽어야 하는 쿼리에서는 스캔이 최선이고, "스캔이 보이니 인덱스를 만들자"는
반사적 반응은 인덱스만 늘리고 쓰기를 느리게 만듭니다.
이 실습은 그 판단을 계획을 보면서 직접 해 보는 것입니다.

단계

1. /root/db/tune.db 를 만들고 /opt/lab/fixtures/dbo/gen.sql 을 실행해
SALES 테이블을 생성합니다. 200,000행 이어야 합니다.
컬럼: SALE_ID, CUST_ID, SALE_DT(문자열 YYYYMMDD), PROD_CD, AMT
2. 아래 쿼리(Q1)의 실행계획을 /root/db/plan1.txt 에 저장합니다.

   SELECT SALE_ID, SALE_DT, AMT FROM SALES    WHERE CUST_ID = 'C000123' AND SALE_DT BETWEEN '20260101' AND '20260630';

계획에 SCAN 이 나타나야 합니다.
(인덱스를 만들기 에 떠야 합니다. 만든 뒤에 뜨면 계획이 달라집니다.)
3. Q1 을 위한 인덱스 IX_SALES_01 을 만들고, 같은 쿼리의 계획을
/root/db/plan2.txt 에 저장합니다. 계획에 IX_SALES_01 이 나타나야 합니다.
4. 인덱스 IX_SALES_BAD컬럼 순서를 반대로 만들고,
/root/db/composite.txt 를 만듭니다. 세 줄입니다.

   good=<Q1 에 적합한 인덱스의 컬럼 순서, 쉼표 구분>   bad=<부적합한 순서>   reason=<한 줄 근거>

5. 아래 쿼리(Q2)를 인덱스를 탈 수 있는 형태로 다시 써
/root/db/rewrite.sql 에 저장합니다.

   SELECT COUNT(*) FROM SALES WHERE substr(SALE_DT,1,6) = '202603';

재작성한 쿼리의 결과가 원래와 같아야 하고,
실행계획을 /root/db/plan3.txt 에 저장했을 때 인덱스를 사용해야 합니다.
(필요하면 인덱스를 추가로 만드세요.)
6. 아래 쿼리(Q3)가 커버링 인덱스만으로 처리되게 만듭니다.

   SELECT SALE_DT, AMT FROM SALES WHERE CUST_ID = 'C000123';

실행계획을 /root/db/covering.txt 에 저장하고,
계획에 COVERING INDEX 가 나타나야 합니다.
7. /opt/lab/fixtures/dbo/gen.sql 이 함께 만든 CUSTOMER_M 테이블을 이용해,
"고객별 총 매출 상위 10명(고객명 포함)"을 구하는 쿼리를
/root/db/join.sql 에 작성하고 결과를 /root/db/join-result.txt 에 저장합니다.
결과는 CUST_ID,CUST_NM,TOTAL 세 컬럼, TOTAL 내림차순 10행입니다.
쿼리는 조인 한 번으로 작성해야 합니다(서브쿼리 반복 금지).
8. /root/db/tuning.md 를 작성합니다.
## 대상, ## 증상, ## 원인, ## 조치, ## 결과 다섯 개의 h2 제목이 있어야 하고,
본문에 아래 세 가지가 들어가야 합니다.

참고

단계 8개

  1. 대용량 테이블 생성
  2. 인덱스 없는 실행계획
  3. 인덱스 생성 후 비교
  4. 복합 인덱스 컬럼 순서
  5. 함수 조건 제거
  6. 커버링 인덱스
  7. N+1 을 조인으로
  8. 튜닝 리포트