
데이터베이스 성능 최적화를 위해 EXPLAIN을 사용하여 SQL 실행 계획을 분석하는 방법을 정리하고자 합니다.
1. EXPLAIN 기본 개념
EXPLAIN은 SQL 실행 계획을 분석하고 최적화할 수 있도록 도와주는 명령어입니다.
EXPLAIN SELECT * FROM 테이블명 WHERE 조건;
또는 실행 시간을 포함한 상세한 분석을 원하면 ANALYZE를 사용할 수 있습니다.
(ANALYZE 사용 가능 버전은 MySQL 8.0+, MariaDB 10.5+ 입니다.😢)
EXPLAIN ANALYZE SELECT * FROM 테이블명 WHERE 조건;
2. EXPLAIN 결과 컬럼 설명
EXPLAIN 쿼리를 수행하면 아래와 같은 컬럼들을 조회할 수 있습니다.
컬럼명 | 설명 |
id | 실행 단계 (조인 시 여러 개의 ID가 존재 가능) |
select_type | 쿼리 유형 (SIMPLE, SUBQUERY, UNION 등) |
table | 조회 대상 테이블 |
type | 조인 및 조회 방식 (ALL, index, range, ref, const 등) |
possible_keys | 사용 가능한 인덱스 |
key | 실제 사용된 인덱스 |
key_len | 사용된 인덱스의 길이 (바이트 단위) |
ref | 인덱스를 통해 비교하는 값 |
rows | 예상 검색 행 개수 (작을수록 좋음) |
Extra | 추가 실행 정보 (Using index, Using filesort, Using temporary 등) |
3. EXPLAIN 결과 해석
☑️ 예시 1: 기본 SELECT 문
✔️ EXPLAIN 쿼리 예시
EXPLAIN SELECT * FROM tbl_user_test WHERE user_id=1;
✔️ EXPLAIN 실행 결과
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
1 | SIMPLE | tbl_user | const | PRIMARY | PRIMARY | 8 | const | 1 |
✔️ 해석
- type = const → PRIMARY KEY(idx)를 이용한 최적화된 단일 행 조회
- rows = 1 → 1개의 행만 조회되므로 성능 최적화 필요 없음
☑️ 예제 2: 풀 테이블 스캔 발생
✔️ EXPLAIN 쿼리 예시
EXPLAIN SELECT * FROM tbl_user_test WHERE username='user1';
✔️ EXPLAIN 실행 결과
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
1 | SIMPLE | tbl_user_test | ALL | NULL | NULL | NULL | NULL | 10000 | Using Where |
✔️ 해석
- type = ALL → 풀 테이블 스캔 발생 (비효율적)
- possible_keys = NULL → 인덱스 사용 안 됨
- rows = 10000 → 조회 예상 행 수가 많음 → 성능 저하
✔️ 해결 방법
조회 조건의 index를 생성 한 후, 다시 EXPLAIN 실행하면 type = ref로 바뀌며 성능 개선이 됩니다.
인덱스 조회 및 생성 방법 (MariaDB)
CREATE INDEX idx_username ON tbl_user_test(username); -- index 생성
SHOW INDEX FROM tbl_user_test; -- index 조회
다시 조회한 결과
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
1 | SIMPLE | tbl_user_test | ref | idx_username | idx_username | 152 | const | 1 | Using index condition |
4. EXPLAIN으로 JOIN 최적화 하기
JOIN을 하게 되면 어떻게 결과가 나올지 확인해보겠습니다.
✔️ EXPLAIN 쿼리 예시
SELECT tot.order_id, tut.username, tot.order_date, tot.total_amount, tot.status
FROM tbl_orders_test tot JOIN tbl_user_test tut
ON tot.user_id = tut.id
WHERE tot.user_id=1;
✔️ EXPLAIN 실행 결과
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
1 | SIMPLE | tut | const | PRIMARY | PRIMARY | 8 | const | 1 | |
1 | SIMPLE | tot | ref | user_id | user_id | 8 | const | 1 |
✔️ 해석
- type = const → 가장 빠른 조회 방식 사용
- tut 테이블에서 PRIMARY KEY를 기준으로 단 하나의 행만 조회 (rows=1)
- 이미 최적화된 상태로 성능 최적화가 필요 없음
5. EXPLAIN으로 쿼리 최적화 여부 확인하는 방법
정리하자면, 아래 항목을 확인해서 쿼리 최적화 여부를 확인할 수 있습니다.
- type이 const, ref → PK 및 인덱스를 활용한 빠른 조회
- key가 NULL이 아님 → 인덱스를 올바르게 활용 중
- rows 값이 1~100 이내 → 불필요한 데이터 스캔 없음
- Extra에 성능 저하 요소 없음 → 추가 최적화 불필요
좀 더 자세히 알아보겠습니다.
1) type이 최적화된 상태인지 확인
type 컬럼의 값이 아래 순서대로 좋은 조회 방식을 나타냅니다.
최적화 정도 | 조회 방식 (type) | 설명 | 해결 방법 |
최적 😊 | const | PK 기반 단일 행 조회 (가장 빠름) | |
좋음 | eq_ref | PK 또는 Unique Key 기반 조인 | |
보통 | ref | 인덱스를 사용한 조인 (성능 양호) | WHERE 절의 컬럼에 인덱스 추가 |
비효율적 | range | 범위 검색 (데이터가 많아지면 느려질 가능성 있음) | 인덱스 최적화 필요 |
문제 있음 | index | 인덱스 전체 스캔 (불필요한 데이터 접근) | 필요한 컬럼만 조회 (SELECT * 안돼요) |
최악 😣 | ALL | 풀 테이블 스캔 (인덱스 사용 안 함) | 인덱스 추가, WHERE 최적화 |
2) key 컬럼이 NULL이 아닌지 확인
- key는 실제 사용된 인덱스를 나타냅니다.
- NULL이면 인덱스가 사용되지 않은 것이라서 최적화가 필요합니다.
3) rows 값이 불필요하게 크지 않은지 확인
- rows 값이 클수록 많은 데이터를 스캔해야 하므로 성능 저하 가능성이 있음
- rows 값이 1~100 정도면 최적화된 상태
- rows 값이 10000 이상이면 인덱스 추가 고려
4) Extra 컬럼에서 성능 저하 요소 확인
Extra 컬럼에 비효율적인 실행 계획이 포함되어 있는지 확인해야 합니다.
친절한 분들입니다.
Extra 값 | 설명 | 해결 방법 |
(없음) | 최적화 완료 | 필요 없음 |
Using index | 인덱스만으로 조회됨 (좋음) | 문제 없음 |
Using where | WHERE 필터링 중 (보통) | 인덱스 추가 고려 |
Using filesort | ORDER BY 최적화 필요 | 인덱스 추가 |
Using temporary | 임시 테이블 생성 (비효율적) | 쿼리 최적화 필요 |
6. 최적화 체크리스트
체크 항목 | 최적화 상태 | 조치 필요 여부 |
type이 좋은 값(const, eq_ref, ref)인지? | ||
key가 NULL이 아닌지? (인덱스 사용 여부) | ||
rows 값이 너무 크지 않은지? | ||
Extra에 성능 저하 요소가 있는지? (Using filesort, Using temporary) |
'BackEnd > Database' 카테고리의 다른 글
SQLD 시험 후기 (누구인가? 누가 안 어렵다 했는가?🤔) (6) | 2023.10.02 |
---|