421. 실행 계획 (Execution Plan) - Full Scan vs Index Scan

⚠️ 이 문서는 "내 쿼리는 왜 이렇게 느릴까?"라는 개발자의 영원한 고민에 대해, 데이터베이스의 옵티마이저가 작성해 준 '쿼리 처리를 위한 구체적인 작업 지시서(실행 계획)'를 읽는 방법과, 그 안에서 가장 중요한 '풀 스캔'과 '인덱스 스캔'의 차이점을 다룹니다.

핵심 인사이트 (3줄 요약)

  1. 본질: 실행 계획(Execution Plan)은 옵티마이저(420번 문서)가 선택한 최적의 경로(How)를 사람의 눈으로 볼 수 있게 트리(Tree) 구조로 출력해 준 것이다.
  2. Full Table Scan (풀 스캔): 인덱스 없이 테이블의 처음부터 끝까지 무식하게 다 뒤지는 방식이다. 데이터가 적거나, 조건에 맞는 데이터가 너무 많을 때(보통 15% 이상) 오히려 효율적이다.
  3. Index Scan (인덱스 스캔): 책의 찾아보기(Index)를 먼저 보고, 필요한 데이터가 있는 디스크 위치로 바로 점프하는 방식이다. 데이터가 1억 건이어도 순식간에 찾아오지만, 찾을 데이터가 너무 많으면 책갈피를 왔다 갔다 하느라(Random Access) 풀 스캔보다 느려진다.

Ⅰ. 개요: 지도를 펴라 (Context & Necessity)

"팀장님, 제가 짠 쿼리가 10초나 걸려요..." 이때 시니어 백엔드 개발자는 무조건 이렇게 말한다. "실행 계획(Explain) 떠봤어?"

우리가 던진 쿼리가 왜 느린지는 소스 코드를 100번 쳐다봐도 알 수 없다. DB 콘솔 창에 EXPLAIN SELECT * FROM Users... 라고 치면, DB는 이 쿼리를 어떻게 실행할 것인지 그 계획표를 보여준다.

  • "1번. A 테이블에서 인덱스를 탄다."
  • "2번. B 테이블과 해시 조인(Hash Join)을 한다."

이 계획표를 읽을 줄 아는 순간, 우리는 '느린 쿼리(Slow Query)'를 잡는 명탐정이 될 수 있다.

📢 섹션 요약 비유: 실행 계획은 **'내비게이션이 알려주는 경로 요약'**과 같습니다. "1. 톨게이트 진입 (예상 시간 5분) $\rightarrow$ 2. 영동고속도로 (예상 시간 1시간) $\rightarrow$ 3. 국도 합류..." 이 지도를 미리 보면 어디서 시간이 가장 많이 지체되는지(병목) 한눈에 알 수 있습니다.


Ⅱ. 실행 계획의 2대 스캔 방식 ★

실행 계획을 열었을 때 가장 눈여겨봐야 할 부분은 **'디스크에서 데이터를 어떻게 읽어왔는가(Access Path)'**다.

1. Full Table Scan (풀 스캔 / 전체 테이블 스캔)

  • 작동 방식: 디스크에 있는 테이블의 첫 번째 블록부터 마지막 블록까지 차례대로(Sequential) 전부 다 읽어 들인다.
  • 언제 발생하나?:
    • WHERE 조건에 인덱스가 아예 없을 때.
    • 인덱스가 있어도, 옵티마이저가 계산해 보니 전체 데이터의 10~15% 이상을 긁어와야 할 때. (이럴 땐 인덱스 타는 게 오히려 더 느리다)
  • 실행 계획 표기: TABLE ACCESS (FULL) (Oracle), type: ALL (MySQL)

2. Index Scan (인덱스 스캔)

  • 작동 방식: 인덱스 트리(B-Tree)를 먼저 타서 데이터가 저장된 '물리적 주소(ROWID)'를 찾은 뒤, 그 주소로 핀포인트 점프(Random Access)하여 데이터를 쏙 뽑아온다.
  • 언제 발생하나?:
    • 극소수의 특정 데이터(예: ID = 5)를 찾을 때.
  • 실행 계획 표기: INDEX (RANGE SCAN) (Oracle), type: ref (MySQL)

Ⅲ. 실행 계획 읽는 법 (Tree 구조)

실행 계획은 보통 '안에서 밖으로', '위에서 아래로' 읽는다.

┌──────────────────────────────────────────────────────────────┐
│           실제 실행 계획(Execution Plan) 읽는 순서 시각화               │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│ [ 📜 실행 계획 출력 결과 (Oracle 예시) ]                             │
│ 1. SELECT STATEMENT                                          │
│ 2.  └─ HASH JOIN                   ◀── (최종 합치기)             │
│ 3.      ├─ TABLE ACCESS (FULL) - 부서  ◀── (먼저 실행됨)         │
│ 4.      └─ INDEX (RANGE SCAN) - 직원  ◀── (먼저 실행됨)          │
│                                                              │
│ ★ 읽는 순서: 가장 깊숙이 들여쓰기 된 것(3, 4)부터 먼저 실행되고,           │
│           같은 깊이일 때는 위에 있는 것(3)부터 실행된다!                 │
└──────────────────────────────────────────────────────────────┘

해석: "부서 테이블은 크기가 작아서 풀 스캔(3번)으로 싹 다 읽고, 직원 테이블은 인덱스(4번)를 타서 필요한 애들만 뽑아온 다음, 마지막에 두 개를 해시 방식(2번)으로 조인했네!"


Ⅳ. 결론

"튜닝의 끝은 결국 실행 계획을 마음대로 조종하는 것이다." 많은 개발자가 "인덱스 = 빠르다, 풀 스캔 = 느리다"라는 잘못된 고정관념을 가지고 있다. 100만 건의 데이터 중 50만 건을 가져올 때 인덱스를 타면, 디스크 바늘이 50만 번 이리저리 왔다 갔다(Random Access) 하느라 서버가 죽어버린다. 차라리 처음부터 끝까지 한 번에 쭉 읽어 내리는(풀 스캔) 것이 10배 이상 빠르다. 실행 계획은 옵티마이저가 이런 딜레마 속에서 내린 '최선의 선택'을 보여주는 성적표다. 이것을 읽지 못하면 SQL 튜닝은 시작조차 할 수 없다.


📌 관련 개념 맵

  • 전제 조건: 옵티마이저 CBO (420번 문서)
  • 인덱스 구조: B-Tree 인덱스 (422번 문서)
  • 조인 방식: Nested Loop Join, Hash Join, Sort Merge Join
  • 주요 명령어: EXPLAIN, EXPLAIN PLAN FOR

👶 어린이를 위한 3줄 비유 설명

  1. 풀 스캔은 두꺼운 백과사전에서 '사자'라는 단어를 찾으려고, 1페이지부터 1000페이지까지 모든 글자를 다 읽어보는 거예요. (무식하지만, 책 전체를 읽어야 할 땐 차라리 이게 낫죠)
  2. 인덱스 스캔은 책 맨 뒤에 있는 '찾아보기(색인)'로 가서 '사자'를 찾은 뒤, "아! 350페이지에 있네!" 하고 그 페이지로 바로 넘어가는 거예요.
  3. 실행 계획(Explain)은 내가 시킨 심부름을 컴퓨터가 '어떤 방법'으로 찾을지 미리 적어둔 일기장이랍니다!